Merge remote-tracking branch 'upstream/master' into osc52

This commit is contained in:
Ayman Bagabas
2024-04-07 23:22:01 +03:00
65 changed files with 1099 additions and 475 deletions
+2
View File
@@ -44,6 +44,8 @@
},
"ignorePatterns": [
"addons/*/src/third-party/*.ts",
"out/*",
"out-test/*",
"**/inwasm-sdks/*",
"**/typings/*.d.ts",
"**/node_modules",
-34
View File
@@ -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:
+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:
```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 -1
View File
@@ -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 -1
View File
@@ -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/"
+14 -2
View File
@@ -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();
+1 -1
View File
@@ -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;
+7 -2
View File
@@ -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 -1
View File
@@ -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/"
+10 -2
View File
@@ -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 -1
View File
@@ -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/"
+2 -2
View File
@@ -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"
+54 -9
View File
@@ -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 -1
View File
@@ -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/"
+8 -13
View File
@@ -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 -1
View File
@@ -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', () => {
+53 -55
View File
@@ -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
View File
@@ -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