mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge remote-tracking branch 'upstream/master' into osc52
This commit is contained in:
@@ -44,6 +44,8 @@
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"addons/*/src/third-party/*.ts",
|
||||
"out/*",
|
||||
"out-test/*",
|
||||
"**/inwasm-sdks/*",
|
||||
"**/typings/*.d.ts",
|
||||
"**/node_modules",
|
||||
|
||||
@@ -150,40 +150,6 @@ jobs:
|
||||
- name: Unit tests
|
||||
run: yarn test-unit --forbid-only
|
||||
|
||||
test-unit:
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16]
|
||||
runs-on: [ubuntu, macos, windows]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Unit tests
|
||||
run: yarn test-unit --forbid-only
|
||||
|
||||
test-api-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
|
||||
@@ -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`.
|
||||
@@ -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>
|
||||
@@ -114,7 +114,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.
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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';
|
||||
@@ -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) {
|
||||
@@ -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,
|
||||
@@ -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();
|
||||
|
||||
@@ -37,7 +37,7 @@ export class CanvasAddon extends Disposable implements ITerminalAddon , ICanvasA
|
||||
const coreService = core.coreService;
|
||||
const optionsService = core.optionsService;
|
||||
const screenElement = core.screenElement!;
|
||||
const linkifier = core.linkifier2;
|
||||
const linkifier = core.linkifier!;
|
||||
|
||||
const unsafeCore = core as any;
|
||||
const bufferService: IBufferService = unsafeCore._bufferService;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||
@@ -22,6 +22,7 @@ import { IRenderLayer } from './Types';
|
||||
export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _devicePixelRatio: number;
|
||||
private _observerDisposable = this.register(new MutableDisposable());
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
@@ -60,7 +61,11 @@ export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
this._devicePixelRatio = this._coreBrowserService.dpr;
|
||||
this._updateDimensions();
|
||||
|
||||
this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));
|
||||
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
||||
this.register(this._coreBrowserService.onWindowChange(w => {
|
||||
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
||||
}));
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
for (const l of this._renderLayers) {
|
||||
l.dispose();
|
||||
|
||||
@@ -28,5 +28,10 @@ test.describe('Canvas Renderer Integration Tests', () => {
|
||||
test.skip(({ browserName }) => browserName === 'webkit');
|
||||
|
||||
injectSharedRendererTests(ctxWrapper);
|
||||
injectSharedRendererTestsStandalone(ctxWrapper);
|
||||
injectSharedRendererTestsStandalone(ctxWrapper, async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.addon = new window.CanvasAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { openTerminal, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
@@ -75,7 +75,15 @@ describe('FitAddon', () => {
|
||||
await page.evaluate(`window.term = new Terminal()`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await loadFit();
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
const dimensions: { cols: number, rows: number } | undefined = await 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.
|
||||
if (dimensions) {
|
||||
assert.isAbove(dimensions.cols, 85);
|
||||
assert.isBelow(dimensions.cols, 88);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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",
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sinon": "^5.0.1",
|
||||
"axios": "^0.21.2",
|
||||
"axios": "^1.6.0",
|
||||
"mkdirp": "0.5.5",
|
||||
"sinon": "6.3.5",
|
||||
"yauzl": "^2.10.0"
|
||||
|
||||
@@ -45,17 +45,36 @@ array-from@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
|
||||
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
|
||||
|
||||
axios@^0.21.2:
|
||||
version "0.21.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
|
||||
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
axios@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102"
|
||||
integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
diff@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
@@ -66,10 +85,10 @@ fd-slicer@~1.1.0:
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.14.8"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
|
||||
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.3"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
|
||||
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
|
||||
|
||||
font-finder@^1.0.3:
|
||||
version "1.0.4"
|
||||
@@ -95,6 +114,15 @@ font-ligatures@^1.4.1:
|
||||
lru-cache "^6.0.0"
|
||||
opentype.js "^0.8.0"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
get-system-fonts@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-system-fonts/-/get-system-fonts-2.0.0.tgz#a43b9a33f05c0715a60176d2aad5ce6e98f0a3c6"
|
||||
@@ -140,6 +168,18 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.12:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
@@ -183,6 +223,11 @@ promise-stream-reader@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
sinon@6.3.5:
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0"
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -9,7 +9,6 @@ import { SerializeAddon } from './SerializeAddon';
|
||||
import { Terminal } from 'browser/public/Terminal';
|
||||
import { SelectionModel } from 'browser/selection/SelectionModel';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
import { OptionsService } from 'common/services/OptionsService';
|
||||
import { ThemeService } from 'browser/services/ThemeService';
|
||||
|
||||
function sgr(...seq: string[]): string {
|
||||
@@ -83,6 +82,36 @@ describe('SerializeAddon', () => {
|
||||
await writeP(terminal, sgr('32') + '> ' + sgr('0'));
|
||||
assert.equal(serializeAddon.serialize(), '\u001b[32m> \u001b[0m');
|
||||
});
|
||||
|
||||
describe('ISerializeOptions.range', () => {
|
||||
it('should serialize the top line', async () => {
|
||||
await writeP(terminal, 'hello\r\nworld');
|
||||
assert.equal(serializeAddon.serialize({
|
||||
range: {
|
||||
start: 0,
|
||||
end: 0
|
||||
}
|
||||
}), 'hello');
|
||||
});
|
||||
it('should serialize multiple lines from the top', async () => {
|
||||
await writeP(terminal, 'hello\r\nworld');
|
||||
assert.equal(serializeAddon.serialize({
|
||||
range: {
|
||||
start: 0,
|
||||
end: 1
|
||||
}
|
||||
}), 'hello\r\nworld');
|
||||
});
|
||||
it('should serialize lines in the middle', async () => {
|
||||
await writeP(terminal, 'hello\r\nworld');
|
||||
assert.equal(serializeAddon.serialize({
|
||||
range: {
|
||||
start: 1,
|
||||
end: 1
|
||||
}
|
||||
}), 'world');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('html', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||
import type { SerializeAddon as ISerializeApi } from '@xterm/addon-serialize';
|
||||
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
||||
import { DEFAULT_ANSI_COLORS } from 'browser/services/ThemeService';
|
||||
import { IAttributeData, IColor } from 'common/Types';
|
||||
|
||||
@@ -21,24 +21,24 @@ abstract class BaseSerializeHandler {
|
||||
) {
|
||||
}
|
||||
|
||||
public serialize(range: IBufferRange): string {
|
||||
public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
|
||||
// we need two of them to flip between old and new cell
|
||||
const cell1 = this._buffer.getNullCell();
|
||||
const cell2 = this._buffer.getNullCell();
|
||||
let oldCell = cell1;
|
||||
|
||||
const startRow = range.start.x;
|
||||
const endRow = range.end.x;
|
||||
const startColumn = range.start.y;
|
||||
const endColumn = range.end.y;
|
||||
const startRow = range.start.y;
|
||||
const endRow = range.end.y;
|
||||
const startColumn = range.start.x;
|
||||
const endColumn = range.end.x;
|
||||
|
||||
this._beforeSerialize(endRow - startRow, startRow, endRow);
|
||||
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const line = this._buffer.getLine(row);
|
||||
if (line) {
|
||||
const startLineColumn = row !== range.start.x ? 0 : startColumn;
|
||||
const endLineColumn = row !== range.end.x ? line.length : endColumn;
|
||||
const startLineColumn = row === range.start.y ? startColumn : 0;
|
||||
const endLineColumn = row === range.end.y ? endColumn: line.length;
|
||||
for (let col = startLineColumn; col < endLineColumn; col++) {
|
||||
const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
|
||||
if (!c) {
|
||||
@@ -54,14 +54,14 @@ abstract class BaseSerializeHandler {
|
||||
|
||||
this._afterSerialize();
|
||||
|
||||
return this._serializeString();
|
||||
return this._serializeString(excludeFinalCursorPosition);
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
|
||||
protected _rowEnd(row: number, isLastRow: boolean): void { }
|
||||
protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { }
|
||||
protected _afterSerialize(): void { }
|
||||
protected _serializeString(): string { return ''; }
|
||||
protected _serializeString(excludeFinalCursorPosition?: boolean): string { return ''; }
|
||||
}
|
||||
|
||||
function equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
||||
@@ -353,7 +353,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected _serializeString(): string {
|
||||
protected _serializeString(excludeFinalCursorPosition: boolean): string {
|
||||
let rowEnd = this._allRows.length;
|
||||
|
||||
// the fixup is only required for data without scrollback
|
||||
@@ -374,29 +374,31 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
}
|
||||
|
||||
// restore the cursor
|
||||
const realCursorRow = this._buffer.baseY + this._buffer.cursorY;
|
||||
const realCursorCol = this._buffer.cursorX;
|
||||
if (!excludeFinalCursorPosition) {
|
||||
const realCursorRow = this._buffer.baseY + this._buffer.cursorY;
|
||||
const realCursorCol = this._buffer.cursorX;
|
||||
|
||||
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol);
|
||||
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol);
|
||||
|
||||
const moveRight = (offset: number): void => {
|
||||
if (offset > 0) {
|
||||
content += `\u001b[${offset}C`;
|
||||
} else if (offset < 0) {
|
||||
content += `\u001b[${-offset}D`;
|
||||
const moveRight = (offset: number): void => {
|
||||
if (offset > 0) {
|
||||
content += `\u001b[${offset}C`;
|
||||
} else if (offset < 0) {
|
||||
content += `\u001b[${-offset}D`;
|
||||
}
|
||||
};
|
||||
const moveDown = (offset: number): void => {
|
||||
if (offset > 0) {
|
||||
content += `\u001b[${offset}B`;
|
||||
} else if (offset < 0) {
|
||||
content += `\u001b[${-offset}A`;
|
||||
}
|
||||
};
|
||||
|
||||
if (cursorMoved) {
|
||||
moveDown(realCursorRow - this._lastCursorRow);
|
||||
moveRight(realCursorCol - this._lastCursorCol);
|
||||
}
|
||||
};
|
||||
const moveDown = (offset: number): void => {
|
||||
if (offset > 0) {
|
||||
content += `\u001b[${offset}B`;
|
||||
} else if (offset < 0) {
|
||||
content += `\u001b[${-offset}A`;
|
||||
}
|
||||
};
|
||||
|
||||
if (cursorMoved) {
|
||||
moveDown(realCursorRow - this._lastCursorRow);
|
||||
moveRight(realCursorCol - this._lastCursorCol);
|
||||
}
|
||||
|
||||
// Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677
|
||||
@@ -419,14 +421,21 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
private _serializeBuffer(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {
|
||||
private _serializeBufferByScrollback(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {
|
||||
const maxRows = buffer.length;
|
||||
const handler = new StringSerializeHandler(buffer, terminal);
|
||||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
|
||||
return this._serializeBufferByRange(terminal, buffer, {
|
||||
start: maxRows - correctRows,
|
||||
end: maxRows - 1
|
||||
}, false);
|
||||
}
|
||||
|
||||
private _serializeBufferByRange(terminal: Terminal, buffer: IBuffer, range: ISerializeRange, excludeFinalCursorPosition: boolean): string {
|
||||
const handler = new StringSerializeHandler(buffer, terminal);
|
||||
return handler.serialize({
|
||||
start: { x: maxRows - correctRows, y: 0 },
|
||||
end: { x: maxRows - 1, y: terminal.cols }
|
||||
});
|
||||
start: { x: 0, y: typeof range.start === 'number' ? range.start : range.start.line },
|
||||
end: { x: terminal.cols, y: typeof range.end === 'number' ? range.end : range.end.line }
|
||||
}, excludeFinalCursorPosition);
|
||||
}
|
||||
|
||||
private _serializeBufferAsHTML(terminal: Terminal, options: Partial<IHTMLSerializeOptions>): string {
|
||||
@@ -438,16 +447,16 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
||||
const scrollback = options.scrollback;
|
||||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
|
||||
return handler.serialize({
|
||||
start: { x: maxRows - correctRows, y: 0 },
|
||||
end: { x: maxRows - 1, y: terminal.cols }
|
||||
start: { x: 0, y: maxRows - correctRows },
|
||||
end: { x: terminal.cols, y: maxRows - 1 }
|
||||
});
|
||||
}
|
||||
|
||||
const selection = this._terminal?.getSelectionPosition();
|
||||
if (selection !== undefined) {
|
||||
return handler.serialize({
|
||||
start: { x: selection.start.y, y: selection.start.x },
|
||||
end: { x: selection.end.y, y: selection.end.x }
|
||||
start: { x: selection.start.x, y: selection.start.y },
|
||||
end: { x: selection.end.x, y: selection.end.y }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -490,12 +499,14 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
||||
}
|
||||
|
||||
// Normal buffer
|
||||
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, options?.scrollback);
|
||||
let content = options?.range
|
||||
? this._serializeBufferByRange(this._terminal, this._terminal.buffer.normal, options.range, true)
|
||||
: this._serializeBufferByScrollback(this._terminal, this._terminal.buffer.normal, options?.scrollback);
|
||||
|
||||
// Alternate buffer
|
||||
if (!options?.excludeAltBuffer) {
|
||||
if (this._terminal.buffer.active.type === 'alternate') {
|
||||
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
|
||||
const alternativeScreenContent = this._serializeBufferByScrollback(this._terminal, this._terminal.buffer.alternate, undefined);
|
||||
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
|
||||
}
|
||||
}
|
||||
@@ -519,19 +530,6 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
||||
public dispose(): void { }
|
||||
}
|
||||
|
||||
|
||||
interface ISerializeOptions {
|
||||
scrollback?: number;
|
||||
excludeModes?: boolean;
|
||||
excludeAltBuffer?: boolean;
|
||||
}
|
||||
|
||||
interface IHTMLSerializeOptions {
|
||||
scrollback: number;
|
||||
onlySelection: boolean;
|
||||
includeGlobalBackground: boolean;
|
||||
}
|
||||
|
||||
export class HTMLSerializeHandler extends BaseSerializeHandler {
|
||||
private _currentRow: string = '';
|
||||
|
||||
|
||||
+19
-2
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
import { Terminal, ITerminalAddon, IMarker, IBufferRange } from '@xterm/xterm';
|
||||
|
||||
declare module '@xterm/addon-serialize' {
|
||||
/**
|
||||
@@ -48,10 +48,16 @@ declare module '@xterm/addon-serialize' {
|
||||
}
|
||||
|
||||
export interface ISerializeOptions {
|
||||
/**
|
||||
* The row range to serialize. The an explicit range is specified, the cursor will get its final
|
||||
* repositioning.
|
||||
*/
|
||||
range?: ISerializeRange;
|
||||
|
||||
/**
|
||||
* The number of rows in the scrollback buffer to serialize, starting from the bottom of the
|
||||
* scrollback buffer. When not specified, all available rows in the scrollback buffer will be
|
||||
* serialized.
|
||||
* serialized. This will be ignored if {@link range} is specified.
|
||||
*/
|
||||
scrollback?: number;
|
||||
|
||||
@@ -85,4 +91,15 @@ declare module '@xterm/addon-serialize' {
|
||||
*/
|
||||
includeGlobalBackground: boolean;
|
||||
}
|
||||
|
||||
export interface ISerializeRange {
|
||||
/**
|
||||
* The line to start serializing (inclusive).
|
||||
*/
|
||||
start: IMarker | number;
|
||||
/**
|
||||
* The line to end serializing (inclusive).
|
||||
*/
|
||||
end: IMarker | number;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user