Compare commits
89
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dec5452fc0 | ||
|
|
81b3b1ec46 | ||
|
|
f57b8e1534 | ||
|
|
c8f11d63b5 | ||
|
|
8ab575621d | ||
|
|
3b08b7dcef | ||
|
|
3d929a1db0 | ||
|
|
2fe3fd13a1 | ||
|
|
f33f502267 | ||
|
|
7c24c58007 | ||
|
|
596634ac41 | ||
|
|
0c8271db49 | ||
|
|
b44d036524 | ||
|
|
9872be0c29 | ||
|
|
f3f4dcaf9f | ||
|
|
64718444e2 | ||
|
|
ea75b99e2b | ||
|
|
564113e053 | ||
|
|
b3de9fa6db | ||
|
|
1f054097f3 | ||
|
|
a250987508 | ||
|
|
21c99d0574 | ||
|
|
0b6fd2e8b2 | ||
|
|
4226e64245 | ||
|
|
a374c6c5af | ||
|
|
454e0749c2 | ||
|
|
59264f1f5b | ||
|
|
16ef64f8bf | ||
|
|
a0f93cdcc4 | ||
|
|
5b911db9cf | ||
|
|
560c0ce3a1 | ||
|
|
94958c2489 | ||
|
|
6426573f45 | ||
|
|
5ab59506aa | ||
|
|
096707d736 | ||
|
|
967d3f59e6 | ||
|
|
9a70816dad | ||
|
|
8e7e4f16e5 | ||
|
|
a9a2a8e3c1 | ||
|
|
92a1bfcf2e | ||
|
|
be38252e37 | ||
|
|
0b590dacd6 | ||
|
|
1faa41eac1 | ||
|
|
fa5eb33cdb | ||
|
|
3494f6b5b0 | ||
|
|
58fc09ff0e | ||
|
|
a47e4905e6 | ||
|
|
1252c69ccc | ||
|
|
be3dd80171 | ||
|
|
2db34c4955 | ||
|
|
8854430df4 | ||
|
|
26e771d075 | ||
|
|
fd13893687 | ||
|
|
5ef8001413 | ||
|
|
e2d1420ff7 | ||
|
|
cf1484d7fd | ||
|
|
e12f007597 | ||
|
|
3a4f9e235d | ||
|
|
7b05c8a1b2 | ||
|
|
b45167e00b | ||
|
|
bc42be2074 | ||
|
|
bc26cc16c1 | ||
|
|
80468eeb4c | ||
|
|
69b11a1a48 | ||
|
|
98d7c25bd5 | ||
|
|
6d1f12cb7b | ||
|
|
4500485e79 | ||
|
|
b4bd92d62c | ||
|
|
1a01e9634f | ||
|
|
e35a9c1e90 | ||
|
|
1dd113b38a | ||
|
|
536eeb4db0 | ||
|
|
3f3f6abfb9 | ||
|
|
c961408bf7 | ||
|
|
6fd6003d6c | ||
|
|
d407bf561f | ||
|
|
a1a8d1abae | ||
|
|
f34d0a255c | ||
|
|
a2cd1ab29c | ||
|
|
99550840d2 | ||
|
|
b0f6aed4a1 | ||
|
|
878e3fc52d | ||
|
|
f04679a42e | ||
|
|
6ebcf79358 | ||
|
|
d4acf2fa45 | ||
|
|
ddf2b7d091 | ||
|
|
5684f95c8e | ||
|
|
cd3dcc52b2 | ||
|
|
56a7195487 |
+19
-27
@@ -11,9 +11,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js 22.x
|
- name: Use Node.js 22.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
./addons/addon-webgl/out/* \
|
./addons/addon-webgl/out/* \
|
||||||
./addons/addon-webgl/out-*st/*
|
./addons/addon-webgl/out-*st/*
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
path: compressed-build.zip
|
path: compressed-build.zip
|
||||||
@@ -79,9 +79,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js 22.x
|
- name: Use Node.js 22.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
@@ -100,16 +100,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js 22.x
|
- name: Use Node.js 22.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
- name: Unzip artifacts
|
- name: Unzip artifacts
|
||||||
@@ -129,6 +129,7 @@ jobs:
|
|||||||
exit $EXIT_CODE
|
exit $EXIT_CODE
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
|
needs: build
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -136,21 +137,16 @@ jobs:
|
|||||||
runs-on: [ubuntu, macos, windows]
|
runs-on: [ubuntu, macos, windows]
|
||||||
runs-on: ${{ matrix.runs-on }}-latest
|
runs-on: ${{ matrix.runs-on }}-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}.x
|
node-version: ${{ matrix.node-version }}.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
- name: Wait for build job
|
- uses: actions/download-artifact@v7
|
||||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
job: build
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
- name: Unzip artifacts
|
- name: Unzip artifacts
|
||||||
@@ -166,6 +162,7 @@ jobs:
|
|||||||
run: npm run test-unit --forbid-only
|
run: npm run test-unit --forbid-only
|
||||||
|
|
||||||
test-integration:
|
test-integration:
|
||||||
|
needs: build
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -174,9 +171,9 @@ jobs:
|
|||||||
browser: [chromium, firefox, webkit]
|
browser: [chromium, firefox, webkit]
|
||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}.x
|
node-version: ${{ matrix.node-version }}.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
@@ -185,12 +182,7 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
- name: Install playwright
|
- name: Install playwright
|
||||||
run: npx playwright install --with-deps ${{ matrix.browser }}
|
run: npx playwright install --with-deps ${{ matrix.browser }}
|
||||||
- name: Wait for build job
|
- uses: actions/download-artifact@v7
|
||||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
job: build
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
- name: Unzip artifacts
|
- name: Unzip artifacts
|
||||||
@@ -240,9 +232,9 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
node-version: [22]
|
node-version: [22]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}.x
|
node-version: ${{ matrix.node-version }}.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
@@ -251,7 +243,7 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
- name: Install playwright
|
- name: Install playwright
|
||||||
run: npx playwright install
|
run: npx playwright install
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
- name: Unzip artifacts
|
- name: Unzip artifacts
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v4
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Use Node.js 22.x
|
- name: Use Node.js 22.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use Node.js 22.x
|
- name: Use Node.js 22.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"import",
|
||||||
"typescript"
|
"typescript"
|
||||||
],
|
],
|
||||||
"categories": {
|
"categories": {
|
||||||
@@ -104,7 +105,8 @@
|
|||||||
"typescript/no-wrapper-object-types": "error",
|
"typescript/no-wrapper-object-types": "error",
|
||||||
"typescript/prefer-as-const": "error",
|
"typescript/prefer-as-const": "error",
|
||||||
"typescript/prefer-namespace-keyword": "error",
|
"typescript/prefer-namespace-keyword": "error",
|
||||||
"typescript/triple-slash-reference": "error"
|
"typescript/triple-slash-reference": "error",
|
||||||
|
"import/no-cycle": "error"
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ The preferred way to report bugs or request features is to use
|
|||||||
|
|
||||||
It's pretty common for maintainers of large open source projects to suffer from burnout, especially when needing to triage a large number of incoming issues instead of actually building things. Here are some of the steps we take to try mitigate this:
|
It's pretty common for maintainers of large open source projects to suffer from burnout, especially when needing to triage a large number of incoming issues instead of actually building things. Here are some of the steps we take to try mitigate this:
|
||||||
|
|
||||||
- Support questions live in [GH discussions](https://github.com/xtermjs/xterm.js/discussions), issues may be transfered there without further comment and core maintainers may or may not participate in discussions.
|
- Support questions live in [GH discussions](https://github.com/xtermjs/xterm.js/discussions), issues may be transferred there without further comment and core maintainers may or may not participate in discussions.
|
||||||
- Issues are strictly for well defined features or bugs that are _actionable_.
|
- Issues are strictly for well defined features or bugs that are _actionable_.
|
||||||
- Sometimes features are out of scope. A common example of this is a niche feature that the pricipal implementation ([VS Code](https://code.visualstudio.com/)) won't leverage and therefore would be difficult to maintain and likely suffer from bitrot. The reporter may not agree with this, but you could always create an addon if that works or maintain your own fork if it comes to that.
|
- Sometimes features are out of scope. A common example of this is a niche feature that the pricipal implementation ([VS Code](https://code.visualstudio.com/)) won't leverage and therefore would be difficult to maintain and likely suffer from bitrot. The reporter may not agree with this, but you could always create an addon if that works or maintain your own fork if it comes to that.
|
||||||
- If a feature does not have a clear way forward or needs more discussion it may be closed ro moved to a discussion.
|
- If a feature does not have a clear way forward or needs more discussion it may be closed ro moved to a discussion.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ test.beforeAll(async ({ browser }) => {
|
|||||||
});
|
});
|
||||||
test.afterAll(async () => await ctx.page.close());
|
test.afterAll(async () => await ctx.page.close());
|
||||||
|
|
||||||
test.describe('Search Tests', () => {
|
test.describe('AttachAddon', () => {
|
||||||
|
|
||||||
test.beforeEach(async () => {
|
test.beforeEach(async () => {
|
||||||
await ctx.proxy.reset();
|
await ctx.proxy.reset();
|
||||||
|
|||||||
@@ -78,13 +78,43 @@ export class BrowserClipboardProvider implements IClipboardProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Once the base64 handling on Uint8Arrays is more common,
|
||||||
|
* remove the btoa/atob fallbacks below.
|
||||||
|
*/
|
||||||
|
interface IUint8ArrayB64 extends Uint8Array {
|
||||||
|
toBase64(): string;
|
||||||
|
}
|
||||||
|
interface IUint8ArrayB64Ctor extends Uint8ArrayConstructor {
|
||||||
|
fromBase64(s: string): IUint8ArrayB64;
|
||||||
|
}
|
||||||
|
|
||||||
export class Base64 implements IBase64 {
|
export class Base64 implements IBase64 {
|
||||||
public encodeText(data: string): string {
|
public encodeText(data: string): string {
|
||||||
return btoa(data);
|
const bytes = new TextEncoder().encode(data) as IUint8ArrayB64;
|
||||||
|
if (bytes.toBase64 !== undefined) {
|
||||||
|
return bytes.toBase64();
|
||||||
|
}
|
||||||
|
let bin = '';
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
bin += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return btoa(bin);
|
||||||
}
|
}
|
||||||
public decodeText(data: string): string {
|
public decodeText(data: string): string {
|
||||||
|
if ((Uint8Array as IUint8ArrayB64Ctor).fromBase64 !== undefined) {
|
||||||
|
try {
|
||||||
|
return new TextDecoder().decode((Uint8Array as IUint8ArrayB64Ctor).fromBase64(data));
|
||||||
|
} catch {}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return atob(data);
|
const bin = atob(data);
|
||||||
|
const bytes = new Uint8Array(bin.length);
|
||||||
|
for (let i = 0; i < bin.length; ++i) {
|
||||||
|
bytes[i] = bin.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
} catch {}
|
} catch {}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ test.describe('ClipboardAddon', () => {
|
|||||||
await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
|
await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||||
}
|
}
|
||||||
await ctx.page.evaluate(`
|
await ctx.page.evaluate(`
|
||||||
window.term.reset()
|
window.term.reset();
|
||||||
window.clipboard?.dispose();
|
window.clipboard?.dispose();
|
||||||
window.clipboard = new ClipboardAddon();
|
window.clipboard = new ClipboardAddon();
|
||||||
window.term.loadAddon(window.clipboard);
|
window.term.loadAddon(window.clipboard);
|
||||||
@@ -92,4 +92,22 @@ test.describe('ClipboardAddon', () => {
|
|||||||
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
|
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('non-ASCII', () => {
|
||||||
|
const testDataEncoded = '4oKsbWzDpMO8dMOf';
|
||||||
|
const testDataDecoded = '€mläütß';
|
||||||
|
test('write simple string', async () => {
|
||||||
|
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`);
|
||||||
|
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);
|
||||||
|
});
|
||||||
|
test('read simple string', async () => {
|
||||||
|
await ctx.page.evaluate(`
|
||||||
|
window.data2 = [];
|
||||||
|
window.term.onData(e => data2.push(e));
|
||||||
|
`);
|
||||||
|
await ctx.page.evaluate((d) => window.navigator.clipboard.writeText(d), testDataDecoded);
|
||||||
|
await ctx.proxy.write(`\x1b]52;c;?\x07`);
|
||||||
|
deepEqual(await ctx.page.evaluate('window.data2'), [`\x1b]52;c;${testDataEncoded}\x07`]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@ declare module '@xterm/addon-clipboard' {
|
|||||||
/**
|
/**
|
||||||
* The clipboard provider interface that enables xterm.js to access the browser clipboard.
|
* The clipboard provider interface that enables xterm.js to access the browser clipboard.
|
||||||
*
|
*
|
||||||
* Note this this clipboard provider does not implement own cut buffers as xterm does,
|
* Note that this clipboard provider does not implement own cut buffers as xterm does,
|
||||||
* but redirect the selection parameter always to navigator.clipboard.
|
* but redirect the selection parameter always to navigator.clipboard.
|
||||||
*/
|
*/
|
||||||
export class BrowserClipboardProvider implements IClipboardProvider{
|
export class BrowserClipboardProvider implements IClipboardProvider{
|
||||||
|
|||||||
@@ -74,14 +74,14 @@ export class FitAddon implements ITerminalAddon, IFitApi {
|
|||||||
: (this._terminal.options.scrollbar?.width ?? ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));
|
: (this._terminal.options.scrollbar?.width ?? ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));
|
||||||
|
|
||||||
const parentElementStyle = _getComputedStyle(this._terminal.element.parentElement);
|
const parentElementStyle = _getComputedStyle(this._terminal.element.parentElement);
|
||||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
const parentElementHeight = Math.max(0, parseInt(parentElementStyle.getPropertyValue('height'), 10) || 0);
|
||||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'), 10) || 0);
|
||||||
const elementStyle = _getComputedStyle(this._terminal.element);
|
const elementStyle = _getComputedStyle(this._terminal.element);
|
||||||
const elementPadding = {
|
const elementPadding = {
|
||||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
top: parseInt(elementStyle.getPropertyValue('padding-top'), 10) || 0,
|
||||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom'), 10) || 0,
|
||||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
right: parseInt(elementStyle.getPropertyValue('padding-right'), 10) || 0,
|
||||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
left: parseInt(elementStyle.getPropertyValue('padding-left'), 10) || 0
|
||||||
};
|
};
|
||||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ terminal.loadAddon(imageAddon);
|
|||||||
therefore palette animations cannot be used.
|
therefore palette animations cannot be used.
|
||||||
|
|
||||||
- **SIXEL Raster Attributes Handling**
|
- **SIXEL Raster Attributes Handling**
|
||||||
If raster attributes were found in the SIXEL data (level 2), the image will always be truncated to the given height/width extend. We deviate here from the specification on purpose, as it allows several processing optimizations. For level 1 SIXEL data without any raster attributes the image can freely grow in width and height up to the last data byte, which has a much higher processing penalty. In general encoding libraries should not create level 1 data anymore and should not produce pixel information beyond the announced height/width extend. Both is discouraged by the >30 years old specification.
|
If raster attributes were found in the SIXEL data (level 2), the image will always be truncated to the given height/width extent. We deviate here from the specification on purpose, as it allows several processing optimizations. For level 1 SIXEL data without any raster attributes the image can freely grow in width and height up to the last data byte, which has a much higher processing penalty. In general encoding libraries should not create level 1 data anymore and should not produce pixel information beyond the announced height/width extent. Both is discouraged by the >30 years old specification.
|
||||||
|
|
||||||
Currently the SIXEL implementation of the addon does not take custom pixel sizes into account, a SIXEL pixel will map 1:1 to a screen pixel.
|
Currently the SIXEL implementation of the addon does not take custom pixel sizes into account, a SIXEL pixel will map 1:1 to a screen pixel.
|
||||||
|
|
||||||
@@ -149,9 +149,9 @@ The addon provides the following API endpoints to retrieve raw image data as can
|
|||||||
|
|
||||||
### Memory Usage
|
### Memory Usage
|
||||||
|
|
||||||
The addon does most image processing in Javascript and therefore can occupy a rather big amount of memory. To get an idea where the memory gets eaten, lets look at the data flow and processing steps:
|
The addon does most image processing in JavaScript and therefore can occupy a rather big amount of memory. To get an idea where the memory gets eaten, let's look at the data flow and processing steps:
|
||||||
- Incomming image data chunk at `term.write` (terminal)
|
- Incoming image data chunk at `term.write` (terminal)
|
||||||
`term.write` might stock up incoming chunks. To circumvent this, you will need proper flow control (see xterm.js docs). Note that with image output it is more likely to run into this issue, as it can create lots of MBs in very short time.
|
`term.write` might stack up incoming chunks. To circumvent this, you will need proper flow control (see xterm.js docs). Note that with image output it is more likely to run into this issue, as it can create lots of MBs in very short time.
|
||||||
- Sequence Parser (terminal)
|
- Sequence Parser (terminal)
|
||||||
The parser operates on a buffer containing up to 2^17 codepoints (~0.5 MB).
|
The parser operates on a buffer containing up to 2^17 codepoints (~0.5 MB).
|
||||||
- Sequence Handler - Chunk Decoding (addon)
|
- Sequence Handler - Chunk Decoding (addon)
|
||||||
@@ -177,18 +177,18 @@ const totalActive = storageBytes + decodingBytes;
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note that browsers have offloading tricks for rarely touched memory segments, esp. `storageBytes` might not directly translate into real memory usage. Usage peaks will happen during active decoding of multiple big images due to the need of 2 full pixel buffers at the same time, which cannot be offloaded. Thus you may want to keep an eye on `pixelLimit` under limited memory conditions.
|
Note that browsers have offloading tricks for rarely touched memory segments, esp. `storageBytes` might not directly translate into real memory usage. Usage peaks will happen during active decoding of multiple big images due to the need of 2 full pixel buffers at the same time, which cannot be offloaded. Thus you may want to keep an eye on `pixelLimit` under limited memory conditions.
|
||||||
Further note that the formulas above do not respect the Javascript object's overhead. Compared to the raw buffer needs the book keeping by these objects is rather small (<<5%).
|
Further note that the formulas above do not respect the JavaScript object's overhead. Compared to the raw buffer needs the bookkeeping by these objects is rather small (<<5%).
|
||||||
|
|
||||||
_Why should I care about memory usage at all?_
|
_Why should I care about memory usage at all?_
|
||||||
Well you don't have to, and it probably will just work fine with the addon defaults. But for bigger integrations, where much more data is held in the Javascript context (like multiple terminals on one page), it is likely to hit the engine's memory limit sooner or later under decoding and/or storage pressure.
|
Well you don't have to, and it probably will just work fine with the addon defaults. But for bigger integrations, where much more data is held in the JavaScript context (like multiple terminals on one page), it is likely to hit the engine's memory limit sooner or later under decoding and/or storage pressure.
|
||||||
|
|
||||||
_How can I adjust the memory usage?_
|
_How can I adjust the memory usage?_
|
||||||
- `pixelLimit`
|
- `pixelLimit`
|
||||||
A constructor setting, thus you would have to anticipate, whether (multiple) terminals in your page gonna do lots of concurrent decoding. Since this is normally not the case and the memory usage is only temporarily peaking, a rather high value should work even with multiple terminals in one page.
|
A constructor setting, thus you would have to anticipate, whether (multiple) terminals in your page gonna do lots of concurrent decoding. Since this is normally not the case and the memory usage only peaks temporarily, a rather high value should work even with multiple terminals in one page.
|
||||||
- `storageLimit`
|
- `storageLimit`
|
||||||
A constructor and a runtime setting. In conjunction with `storageUsage` you can do runtime checks and adjust the limit to your needs. If you have to lower the limit below the current usage, images will be removed in FIFO order and may turn into a placeholder in the terminal's scrollback (if `showPlaceholder` is set). When adjusting keep in mind to leave enough room for memory peaking for decoding.
|
A constructor and a runtime setting. In conjunction with `storageUsage` you can do runtime checks and adjust the limit to your needs. If you have to lower the limit below the current usage, images will be removed in FIFO order and may turn into a placeholder in the terminal's scrollback (if `showPlaceholder` is set). When adjusting keep in mind to leave enough room for peak memory use during decoding.
|
||||||
- `sixelSizeLimit | iipSizeLimit | kittySizeLimit`
|
- `sixelSizeLimit | iipSizeLimit | kittySizeLimit`
|
||||||
Constructor settings. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken image sequences at an earlier phase, thus may stop the invoked decoders from entering memory intensive tasks for potentially invalid data.
|
Constructor settings. This has only a small impact on peak memory during decoding. It is meant to avoid processing of overly big or broken image sequences at an earlier phase, thus may stop the invoked decoders from entering memory intensive tasks for potentially invalid data.
|
||||||
|
|
||||||
|
|
||||||
### Terminal Interaction
|
### Terminal Interaction
|
||||||
|
|||||||
@@ -222,8 +222,8 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
|
|
||||||
private _dim(s: string, total: number, cdim: number): number {
|
private _dim(s: string, total: number, cdim: number): number {
|
||||||
if (s === 'auto') return 0;
|
if (s === 'auto') return 0;
|
||||||
if (s.endsWith('%')) return parseInt(s.slice(0, -1)) * total / 100;
|
if (s.endsWith('%')) return parseInt(s.slice(0, -1), 10) * total / 100;
|
||||||
if (s.endsWith('px')) return parseInt(s.slice(0, -2));
|
if (s.endsWith('px')) return parseInt(s.slice(0, -2), 10);
|
||||||
return parseInt(s) * cdim;
|
return parseInt(s, 10) * cdim;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,9 +436,9 @@ export class ImageStorage implements IDisposable {
|
|||||||
for (let col = 0; col < cols; ++col) {
|
for (let col = 0; col < cols; ++col) {
|
||||||
let e: IExtendedAttrsImage;
|
let e: IExtendedAttrsImage;
|
||||||
if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
|
if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
|
||||||
e = line._extendedAttrs[col] ?? EMPTY_ATTRS;
|
e = line._extendedAttrs?.[col] ?? EMPTY_ATTRS;
|
||||||
} else {
|
} else {
|
||||||
const maybeImg = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
|
const maybeImg = line._extendedAttrs?.[col] as IExtendedAttrsImage | undefined;
|
||||||
if (!maybeImg || maybeImg.imageId === undefined || maybeImg.imageId === -1) {
|
if (!maybeImg || maybeImg.imageId === undefined || maybeImg.imageId === -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -461,7 +461,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
* Also check _extendedAttrs directly for cells where text cleared HAS_EXTENDED.
|
* Also check _extendedAttrs directly for cells where text cleared HAS_EXTENDED.
|
||||||
*/
|
*/
|
||||||
while (++col < cols) {
|
while (++col < cols) {
|
||||||
const nextE = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
|
const nextE = line._extendedAttrs?.[col] as IExtendedAttrsImage | undefined;
|
||||||
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -515,7 +515,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
for (let row = 0; row < rows; ++row) {
|
for (let row = 0; row < rows; ++row) {
|
||||||
const line = buffer.lines.get(row) as IBufferLineExt;
|
const line = buffer.lines.get(row) as IBufferLineExt;
|
||||||
if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {
|
if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {
|
||||||
const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS;
|
const e: IExtendedAttrsImage = line._extendedAttrs?.[oldCol] ?? EMPTY_ATTRS;
|
||||||
const imageId = e.imageId;
|
const imageId = e.imageId;
|
||||||
if (imageId === undefined || imageId === -1) {
|
if (imageId === undefined || imageId === -1) {
|
||||||
continue;
|
continue;
|
||||||
@@ -560,7 +560,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
const buffer = this._terminal._core.buffer;
|
const buffer = this._terminal._core.buffer;
|
||||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||||
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
||||||
const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS;
|
const e: IExtendedAttrsImage = line._extendedAttrs?.[x] ?? EMPTY_ATTRS;
|
||||||
if (e.imageId && e.imageId !== -1) {
|
if (e.imageId && e.imageId !== -1) {
|
||||||
const orig = this._images.get(e.imageId)?.orig;
|
const orig = this._images.get(e.imageId)?.orig;
|
||||||
if (window.ImageBitmap && orig instanceof ImageBitmap) {
|
if (window.ImageBitmap && orig instanceof ImageBitmap) {
|
||||||
@@ -580,7 +580,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
const buffer = this._terminal._core.buffer;
|
const buffer = this._terminal._core.buffer;
|
||||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||||
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
||||||
const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS;
|
const e: IExtendedAttrsImage = line._extendedAttrs?.[x] ?? EMPTY_ATTRS;
|
||||||
if (e.imageId && e.imageId !== -1 && e.tileId !== -1) {
|
if (e.imageId && e.imageId !== -1 && e.tileId !== -1) {
|
||||||
const spec = this._images.get(e.imageId);
|
const spec = this._images.get(e.imageId);
|
||||||
if (spec) {
|
if (spec) {
|
||||||
@@ -611,7 +611,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
|
|
||||||
private _writeToCell(line: IBufferLineExt, x: number, imageId: number, tileId: number): void {
|
private _writeToCell(line: IBufferLineExt, x: number, imageId: number, tileId: number): void {
|
||||||
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||||
const old = line._extendedAttrs[x];
|
const old = line._extendedAttrs?.[x];
|
||||||
if (old) {
|
if (old) {
|
||||||
if (old.imageId !== undefined) {
|
if (old.imageId !== undefined) {
|
||||||
// found an old ExtendedAttrsImage, since we know that
|
// found an old ExtendedAttrsImage, since we know that
|
||||||
@@ -627,13 +627,13 @@ export class ImageStorage implements IDisposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// found a plain ExtendedAttrs instance, clone it to new entry
|
// found a plain ExtendedAttrs instance, clone it to new entry
|
||||||
line._extendedAttrs[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);
|
(line._extendedAttrs ??= {})[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fall-through: always create new ExtendedAttrsImage entry
|
// fall-through: always create new ExtendedAttrsImage entry
|
||||||
line._data[x * Cell.SIZE + Cell.BG] |= BgFlags.HAS_EXTENDED;
|
line._data[x * Cell.SIZE + Cell.BG] |= BgFlags.HAS_EXTENDED;
|
||||||
line._extendedAttrs[x] = new ExtendedAttrsImage(0, 0, imageId, tileId);
|
(line._extendedAttrs ??= {})[x] = new ExtendedAttrsImage(0, 0, imageId, tileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _evictOnAlternate(): void {
|
private _evictOnAlternate(): void {
|
||||||
@@ -652,7 +652,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
}
|
}
|
||||||
for (let x = 0; x < this._terminal.cols; ++x) {
|
for (let x = 0; x < this._terminal.cols; ++x) {
|
||||||
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||||
const imgId = line._extendedAttrs[x]?.imageId;
|
const imgId = line._extendedAttrs?.[x]?.imageId;
|
||||||
if (imgId) {
|
if (imgId) {
|
||||||
const spec = this._images.get(imgId);
|
const spec = this._images.get(imgId);
|
||||||
if (spec) {
|
if (spec) {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
|
|||||||
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
|
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
|
||||||
import type { AttributeData } from 'common/buffer/AttributeData';
|
import type { AttributeData } from 'common/buffer/AttributeData';
|
||||||
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
||||||
import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
|
import type { IInputHandler } from 'common/Types';
|
||||||
|
import type { IBufferLine, IExtendedAttrs } from 'common/buffer/Types';
|
||||||
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
|
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
|
||||||
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
|
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||||
@@ -63,7 +64,7 @@ export interface IExtendedAttrsImage extends IExtendedAttrs {
|
|||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export interface IBufferLineExt extends IBufferLine {
|
export interface IBufferLineExt extends IBufferLine {
|
||||||
_extendedAttrs: {[index: number]: IExtendedAttrsImage | undefined};
|
_extendedAttrs: {[index: number]: IExtendedAttrsImage | undefined} | undefined;
|
||||||
_data: Uint32Array;
|
_data: Uint32Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export function parseKittyCommand(data: string): IKittyCommand {
|
|||||||
cmd.deleteSelector = value;
|
cmd.deleteSelector = value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const numValue = parseInt(value);
|
const numValue = parseInt(value, 10);
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case KittyKey.FORMAT: cmd.format = numValue; break;
|
case KittyKey.FORMAT: cmd.format = numValue; break;
|
||||||
case KittyKey.ID: cmd.id = numValue; break;
|
case KittyKey.ID: cmd.id = numValue; break;
|
||||||
|
|||||||
@@ -118,7 +118,10 @@ test.describe('ImageAddon', () => {
|
|||||||
}
|
}
|
||||||
// bufferline privates
|
// bufferline privates
|
||||||
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
|
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
|
||||||
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);
|
strictEqual(await ctx.page.evaluate(`(() => {
|
||||||
|
const ea = window.term._core.buffer.lines.get(0)._extendedAttrs;
|
||||||
|
return ea === undefined || (typeof ea === 'object' && ea !== null);
|
||||||
|
})()`), true);
|
||||||
// inputhandler privates
|
// inputhandler privates
|
||||||
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), '_AttributeData');
|
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), '_AttributeData');
|
||||||
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
|
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const CACHE_SIZE = 100000;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable ligature support for the provided Terminal instance. To function
|
* Enable ligature support for the provided Terminal instance. To function
|
||||||
* properly, this must be called after `open()` is called on the therminal. If
|
* properly, this must be called after `open()` is called on the terminal. If
|
||||||
* the font currently in use supports ligatures, the terminal will automatically
|
* the font currently in use supports ligatures, the terminal will automatically
|
||||||
* start to render them.
|
* start to render them.
|
||||||
* @param term Terminal instance from xterm.js
|
* @param term Terminal instance from xterm.js
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { DecorationManager } from './DecorationManager';
|
||||||
|
import { SearchEngine } from './SearchEngine';
|
||||||
|
import { SearchLineCache } from './SearchLineCache';
|
||||||
|
import { Terminal } from 'browser/public/Terminal';
|
||||||
|
import type { ISearchDecorationOptions } from '@xterm/addon-search';
|
||||||
|
import type { IDecorationOptions } from '@xterm/xterm';
|
||||||
|
import { DisposableStore } from 'common/Lifecycle';
|
||||||
|
|
||||||
|
function writeP(terminal: Terminal, data: string): Promise<void> {
|
||||||
|
return new Promise(r => terminal.write(data, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DecorationManager', () => {
|
||||||
|
let store: DisposableStore;
|
||||||
|
let terminal: Terminal;
|
||||||
|
let decorationManager: DecorationManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = new DisposableStore();
|
||||||
|
terminal = store.add(new Terminal({ cols: 10, rows: 5 }));
|
||||||
|
decorationManager = store.add(new DecorationManager(terminal));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should split highlight decorations for a wrapped match', async () => {
|
||||||
|
await writeP(terminal, '0123456789abcde');
|
||||||
|
const searchEngine = new SearchEngine(terminal, store.add(new SearchLineCache(terminal)));
|
||||||
|
const match = searchEngine.find('9abc', 0, 0);
|
||||||
|
assert.ok(match);
|
||||||
|
|
||||||
|
const decorationOptions: IDecorationOptions[] = [];
|
||||||
|
const registerDecoration = terminal.registerDecoration.bind(terminal);
|
||||||
|
terminal.registerDecoration = (options: IDecorationOptions) => {
|
||||||
|
decorationOptions.push(options);
|
||||||
|
return registerDecoration(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ISearchDecorationOptions = {
|
||||||
|
matchOverviewRuler: '#ff0000',
|
||||||
|
activeMatchColorOverviewRuler: '#00ff00'
|
||||||
|
};
|
||||||
|
decorationManager.createHighlightDecorations([match], options);
|
||||||
|
|
||||||
|
assert.strictEqual(decorationOptions.length, 2);
|
||||||
|
assert.strictEqual(decorationOptions[0].x, 9);
|
||||||
|
assert.strictEqual(decorationOptions[0].width, 1);
|
||||||
|
assert.strictEqual(decorationOptions[1].x, 0);
|
||||||
|
assert.strictEqual(decorationOptions[1].width, 3);
|
||||||
|
|
||||||
|
const withOverviewRuler = decorationOptions.filter(o => o.overviewRulerOptions !== undefined);
|
||||||
|
assert.strictEqual(withOverviewRuler.length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only add one overview ruler marker per buffer line', async () => {
|
||||||
|
await writeP(terminal, 'abcdefghij');
|
||||||
|
const decorationOptions: IDecorationOptions[] = [];
|
||||||
|
const registerDecoration = terminal.registerDecoration.bind(terminal);
|
||||||
|
terminal.registerDecoration = (options: IDecorationOptions) => {
|
||||||
|
decorationOptions.push(options);
|
||||||
|
return registerDecoration(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ISearchDecorationOptions = {
|
||||||
|
matchOverviewRuler: '#ff0000',
|
||||||
|
activeMatchColorOverviewRuler: '#00ff00'
|
||||||
|
};
|
||||||
|
decorationManager.createHighlightDecorations([
|
||||||
|
{ term: 'a', col: 0, row: 0, size: 1 },
|
||||||
|
{ term: 'f', col: 5, row: 0, size: 1 }
|
||||||
|
], options);
|
||||||
|
|
||||||
|
const withOverviewRuler = decorationOptions.filter(o => o.overviewRulerOptions !== undefined);
|
||||||
|
assert.strictEqual(withOverviewRuler.length, 1);
|
||||||
|
assert.strictEqual(withOverviewRuler[0].x, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -60,7 +60,7 @@ export class SearchEngine {
|
|||||||
this._terminal.clearSelection();
|
this._terminal.clearSelection();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (startCol > this._terminal.cols) {
|
if (startCol >= this._terminal.cols) {
|
||||||
throw new Error(`Invalid col: ${startCol} to search in terminal of ${this._terminal.cols} cols`);
|
throw new Error(`Invalid col: ${startCol} to search in terminal of ${this._terminal.cols} cols`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ test.describe('Search Tests', () => {
|
|||||||
test.describe('#2444 wrapped line content not being found', () => {
|
test.describe('#2444 wrapped line content not being found', () => {
|
||||||
let fixture: string;
|
let fixture: string;
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
fixture = (await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)))).toString();
|
fixture = (await new Promise<Buffer>((res, rej) => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => err ? rej(err) : res(data)))).toString();
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
fixture = fixture.replace(/\n/g, '\n\r');
|
fixture = fixture.replace(/\n/g, '\n\r');
|
||||||
}
|
}
|
||||||
@@ -501,7 +501,7 @@ test.describe('Search Tests', () => {
|
|||||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||||
`);
|
`);
|
||||||
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
|
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
|
||||||
await ctx.proxy.write('\\x1b[CHi Hi');
|
await ctx.proxy.write('\x1b[CHi Hi');
|
||||||
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
strictEqual(await ctx.page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||||
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
|
||||||
{ resultCount: 2, resultIndex: 1 }
|
{ resultCount: 2, resultIndex: 1 }
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
|
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||||
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
||||||
import { IAttributeData, IColor } from 'common/Types';
|
import { IColor } from 'common/Types';
|
||||||
|
import { IAttributeData } from 'common/buffer/Types';
|
||||||
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
|
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
|
||||||
import { UnderlineStyle } from 'common/buffer/Constants';
|
import { UnderlineStyle } from 'common/buffer/Constants';
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
|
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
|
||||||
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
|
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
|
||||||
protected _rowEnd(row: number, isLastRow: boolean): void {
|
protected _rowEnd(row: number, isLastRow: boolean): void {
|
||||||
// if there is colorful empty cell at line end, whe must pad it back, or the the color block
|
// if there is colorful empty cell at line end, we must pad it back, or the color block
|
||||||
// will missing
|
// will missing
|
||||||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||||
// use clear right to set background.
|
// use clear right to set background.
|
||||||
@@ -220,7 +221,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
// you can't use control sequence to move cursor to (x === row)
|
// you can't use control sequence to move cursor to (x === row)
|
||||||
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) &&
|
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) &&
|
||||||
// change background of the first wrapped cell also affects BCE
|
// change background of the first wrapped cell also affects BCE
|
||||||
// so we mark it as invalid to simply the process to determine line separator
|
// so we mark it as invalid to simplify the process to determine line separator
|
||||||
equalBg(thisRowLastChar, nextRowFirstChar)
|
equalBg(thisRowLastChar, nextRowFirstChar)
|
||||||
) {
|
) {
|
||||||
isValid = true;
|
isValid = true;
|
||||||
@@ -232,7 +233,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
isNextRowFirstCharDoubleWidth &&
|
isNextRowFirstCharDoubleWidth &&
|
||||||
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) &&
|
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) &&
|
||||||
// change background of the first wrapped cell also affects BCE
|
// change background of the first wrapped cell also affects BCE
|
||||||
// so we mark it as invalid to simply the process to determine line separator
|
// so we mark it as invalid to simplify the process to determine line separator
|
||||||
equalBg(thisRowLastChar, nextRowFirstChar) &&
|
equalBg(thisRowLastChar, nextRowFirstChar) &&
|
||||||
equalBg(thisRowLastSecondChar, nextRowFirstChar)
|
equalBg(thisRowLastSecondChar, nextRowFirstChar)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ test.describe('SerializeAddon', () => {
|
|||||||
const cols = 10;
|
const cols = 10;
|
||||||
const line = '+'.repeat(cols);
|
const line = '+'.repeat(cols);
|
||||||
const lines: string[] = [
|
const lines: string[] = [
|
||||||
sgr(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
|
sgr(FG_P16_GREEN) + line, // Workaround: If we clear all flags at the end, serialize will use \x1b[0m to clear instead of the specific disable sequence
|
||||||
sgr(INVERSE) + line,
|
sgr(INVERSE) + line,
|
||||||
sgr(BOLD) + line,
|
sgr(BOLD) + line,
|
||||||
sgr(UNDERLINED) + line,
|
sgr(UNDERLINED) + line,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*
|
*
|
||||||
* UnicodeVersionProvider for V15 with grapeme cluster handleing.
|
* UnicodeVersionProvider for V15 with grapheme cluster handling.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Terminal, ITerminalAddon, IUnicodeHandling } from '@xterm/xterm';
|
import type { Terminal, ITerminalAddon, IUnicodeHandling } from '@xterm/xterm';
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ function isUrl(urlString: string): boolean {
|
|||||||
|
|
||||||
export class LinkComputer {
|
export class LinkComputer {
|
||||||
public static computeLink(y: number, regex: RegExp, terminal: Terminal, activate: (event: MouseEvent, uri: string) => void): ILink[] {
|
public static computeLink(y: number, regex: RegExp, terminal: Terminal, activate: (event: MouseEvent, uri: string) => void): ILink[] {
|
||||||
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
|
const flags = regex.flags.includes('g') ? regex.flags : `${regex.flags}g`;
|
||||||
|
const rex = new RegExp(regex.source, flags);
|
||||||
|
|
||||||
const [lines, startLineIndex] = LinkComputer._getWindowedLineStrings(y - 1, terminal);
|
const [lines, startLineIndex] = LinkComputer._getWindowedLineStrings(y - 1, terminal);
|
||||||
const line = lines.join('');
|
const line = lines.join('');
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
|
|||||||
import type { WebLinksAddon as IWebLinksApi } from '@xterm/addon-web-links';
|
import type { WebLinksAddon as IWebLinksApi } from '@xterm/addon-web-links';
|
||||||
import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';
|
import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';
|
||||||
|
|
||||||
// consider everthing starting with http:// or https://
|
// consider everything starting with http:// or https://
|
||||||
// up to first whitespace, `"` or `'` as url
|
// up to first whitespace, `"` or `'` as url
|
||||||
// NOTE: The repeated end clause is needed to not match a dangling `:`
|
// NOTE: The repeated end clause is needed to not match a dangling `:`
|
||||||
// resembling the old (...)*([^:"\'\\s]) final path clause
|
// resembling the old (...)*([^:"\'\\s]) final path clause
|
||||||
// additionally exclude early + final:
|
// additionally exclude early + final:
|
||||||
// - unsafe from rfc3986: !*'()
|
// - unsafe from rfc3986: !*'()
|
||||||
// - unsafe chars from rfc1738: {}|\^~[]` (minus [] as we need them for ipv6 adresses, also allow ~)
|
// - unsafe chars from rfc1738: {}|\^~[]` (minus [] for ipv6 addresses, also allow ~)
|
||||||
// also exclude as finals:
|
// also exclude as finals:
|
||||||
// - final interpunction like ,.!?
|
// - final interpunction like ,.!?
|
||||||
// - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)
|
// - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-
|
|||||||
|
|
||||||
### Handling Context Loss
|
### Handling Context Loss
|
||||||
|
|
||||||
The browser may drop WebGL contexts for various reasons like OOM or after the system has been suspended. There is an API exposed that fires the `webglcontextlost` event fired on the canvas so embedders can handle it however they wish. An easy, but suboptimal way, to handle this is by disposing of WebglAddon when the event fires:
|
The browser may drop WebGL contexts for various reasons like OOM or after the system has been suspended. There is an API exposed that fires the `webglcontextlost` event on the canvas so embedders can handle it however they wish. An easy, but suboptimal way, to handle this is by disposing of WebglAddon when the event fires:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const terminal = new Terminal();
|
const terminal = new Terminal();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|||||||
import { ReadonlyColorSet } from 'browser/Types';
|
import { ReadonlyColorSet } from 'browser/Types';
|
||||||
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
||||||
import { IDecorationService, IOptionsService } from 'common/services/Services';
|
import { IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
import { ICellData } from 'common/Types';
|
import { ICellData } from 'common/buffer/Types';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { rgba } from 'common/Color';
|
import { rgba } from 'common/Color';
|
||||||
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export class GlyphRenderer extends Disposable {
|
|||||||
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
|
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 one-off for
|
||||||
// each glyph.
|
// each glyph.
|
||||||
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg);
|
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg);
|
||||||
}
|
}
|
||||||
@@ -389,8 +389,9 @@ export class GlyphRenderer extends Disposable {
|
|||||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture);
|
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas);
|
||||||
gl.generateMipmap(gl.TEXTURE_2D);
|
|
||||||
this._atlasTextures[i].version = atlas.pages[i].version;
|
this._atlasTextures[i].version = atlas.pages[i].version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
private _overflowSizePage: AtlasPage | undefined;
|
private _overflowSizePage: AtlasPage | undefined;
|
||||||
|
|
||||||
private _tmpCanvas: HTMLCanvasElement;
|
private _tmpCanvas: HTMLCanvasElement;
|
||||||
// A temporary context that glyphs are drawn to before being transfered to the atlas.
|
// A temporary context that glyphs are drawn to before being transferred to the atlas.
|
||||||
private _tmpCtx: CanvasRenderingContext2D;
|
private _tmpCtx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
|
private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
|
||||||
@@ -152,7 +152,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _createNewPage(): AtlasPage {
|
private _createNewPage(): AtlasPage {
|
||||||
// Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
|
// Try merge the set of the 4 most used pages of the largest size. This is deferred to a
|
||||||
// microtask to ensure it does not interrupt textures that will be rendered in the current
|
// microtask to ensure it does not interrupt textures that will be rendered in the current
|
||||||
// animation frame which would result in blank rendered areas. This is actually not that
|
// animation frame which would result in blank rendered areas. This is actually not that
|
||||||
// expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
|
// expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
|
||||||
@@ -191,7 +191,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
return newPage;
|
return newPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1);
|
const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a - b);
|
||||||
const mergedPageIndex = this.pages.length - mergingPages.length;
|
const mergedPageIndex = this.pages.length - mergingPages.length;
|
||||||
|
|
||||||
// Merge into the new page
|
// Merge into the new page
|
||||||
@@ -1080,17 +1080,33 @@ class AtlasPage {
|
|||||||
size: number,
|
size: number,
|
||||||
sourcePages?: AtlasPage[]
|
sourcePages?: AtlasPage[]
|
||||||
) {
|
) {
|
||||||
if (sourcePages) {
|
|
||||||
for (const p of sourcePages) {
|
|
||||||
this._glyphs.push(...p.glyphs);
|
|
||||||
this._usedPixels += p._usedPixels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.canvas = createCanvas(document, size, size);
|
this.canvas = createCanvas(document, size, size);
|
||||||
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
||||||
// It might also contain some characters with transparent backgrounds if allowTransparency is
|
// It might also contain some characters with transparent backgrounds if allowTransparency is
|
||||||
// set.
|
// set.
|
||||||
this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
|
this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
|
||||||
|
if (sourcePages) {
|
||||||
|
if (sourcePages.length === 4) {
|
||||||
|
// optimized for quadmerge
|
||||||
|
this._glyphs = this._glyphs.concat(
|
||||||
|
sourcePages[0].glyphs,
|
||||||
|
sourcePages[1].glyphs,
|
||||||
|
sourcePages[2].glyphs,
|
||||||
|
sourcePages[3].glyphs
|
||||||
|
);
|
||||||
|
this._usedPixels = sourcePages[0]._usedPixels +
|
||||||
|
sourcePages[1]._usedPixels +
|
||||||
|
sourcePages[2]._usedPixels +
|
||||||
|
sourcePages[3]._usedPixels;
|
||||||
|
} else {
|
||||||
|
// fallback for non quadmerges (should never be used)
|
||||||
|
for (let i = 0; i < sourcePages.length; ++i) {
|
||||||
|
this._glyphs = this._glyphs.concat(sourcePages[i].glyphs);
|
||||||
|
this._usedPixels += sourcePages[i]._usedPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
@@ -1125,7 +1141,7 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol
|
|||||||
// were covered (fg=#8ae234, bg=#c4a000).
|
// were covered (fg=#8ae234, bg=#c4a000).
|
||||||
const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
|
const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
|
||||||
|
|
||||||
// Set alpha channel of relevent pixels to 0
|
// Set alpha channel of relevant pixels to 0
|
||||||
let isEmpty = true;
|
let isEmpty = true;
|
||||||
for (let offset = 0; offset < imageData.data.length; offset += 4) {
|
for (let offset = 0; offset < imageData.data.length; offset += 4) {
|
||||||
// Check exact match
|
// Check exact match
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
|
|||||||
};
|
};
|
||||||
const gl = document.createElement('canvas').getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
|
const gl = document.createElement('canvas').getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
|
||||||
if (!gl) {
|
if (!gl) {
|
||||||
throw new Error('Webgl2 is only supported on Safari 16 and above');
|
throw new Error('WebGL2 is only supported on Safari 16 and above');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { CursorBlinkStateManager } from './CursorBlinkStateManager';
|
|||||||
import { observeDevicePixelDimensions } from './DevicePixelObserver';
|
import { observeDevicePixelDimensions } from './DevicePixelObserver';
|
||||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||||
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
import { CharData, IBufferLine, ICellData } from 'common/buffer/Types';
|
||||||
import { AttributeData } from 'common/buffer/AttributeData';
|
import { AttributeData } from 'common/buffer/AttributeData';
|
||||||
import { CellData } from 'common/buffer/CellData';
|
import { CellData } from 'common/buffer/CellData';
|
||||||
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||||
@@ -456,7 +456,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
|
|
||||||
for (y = start; y <= end; y++) {
|
for (y = start; y <= end; y++) {
|
||||||
row = y + terminal.buffer.ydisp;
|
row = y + terminal.buffer.ydisp;
|
||||||
line = terminal.buffer.lines.get(row)!;
|
const bufferLine = terminal.buffer.lines.get(row);
|
||||||
|
if (!bufferLine) {
|
||||||
|
this._model.lineLengths[y] = 0;
|
||||||
|
for (x = 0; x < terminal.cols; x++) {
|
||||||
|
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
||||||
|
modelUpdated = true;
|
||||||
|
this._nullModelCell(x, y, j, 0, 0, 0);
|
||||||
|
}
|
||||||
|
this._setRowBlinkState(y, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
line = bufferLine;
|
||||||
let rowHasBlinkingCells = false;
|
let rowHasBlinkingCells = false;
|
||||||
this._model.lineLengths[y] = 0;
|
this._model.lineLengths[y] = 0;
|
||||||
isCursorRow = cursorY === row;
|
isCursorRow = cursorY === row;
|
||||||
@@ -587,13 +598,9 @@ 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) * RenderModelConstants.INDICIES_PER_CELL;
|
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
||||||
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
|
|
||||||
this._model.cells[j] = NULL_CELL_CODE;
|
|
||||||
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not
|
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not
|
||||||
// supported
|
// supported
|
||||||
this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
|
this._nullModelCell(x, y, j, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext);
|
||||||
this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
|
|
||||||
this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
|
|
||||||
}
|
}
|
||||||
x--; // Go back to the previous update cell for next iteration
|
x--; // Go back to the previous update cell for next iteration
|
||||||
}
|
}
|
||||||
@@ -607,6 +614,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._updateTextBlinkState();
|
this._updateTextBlinkState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _nullModelCell(x: number, y: number, cellIndex: number, bg: number, fg: number, ext: number): void {
|
||||||
|
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, bg, fg, ext, NULL_CELL_CHAR, 0, 0);
|
||||||
|
this._model.cells[cellIndex] = NULL_CELL_CODE;
|
||||||
|
this._model.cells[cellIndex + RenderModelConstants.BG_OFFSET] = bg;
|
||||||
|
this._model.cells[cellIndex + RenderModelConstants.FG_OFFSET] = fg;
|
||||||
|
this._model.cells[cellIndex + RenderModelConstants.EXT_OFFSET] = ext;
|
||||||
|
}
|
||||||
|
|
||||||
private _resetBlinkingRowState(): void {
|
private _resetBlinkingRowState(): void {
|
||||||
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
||||||
this._rowHasBlinkingCellsCount = 0;
|
this._rowHasBlinkingCellsCount = 0;
|
||||||
@@ -664,10 +679,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;
|
this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;
|
||||||
this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;
|
this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;
|
||||||
|
|
||||||
// The the size of the canvas on the page. It's important that this rounds to nearest integer
|
// The size of the canvas on the page. It's important that this rounds to nearest integer
|
||||||
// and not ceils as browsers often have floating point precision issues where
|
// and not ceils as browsers often have floating point precision issues where
|
||||||
// `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
|
// `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
|
||||||
// it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
|
// it's actually 1.1. Ceiling may cause blurriness as the backing canvas image is 1 pixel too
|
||||||
// large for the canvas element size.
|
// large for the canvas element size.
|
||||||
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);
|
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);
|
||||||
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);
|
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);
|
||||||
|
|||||||
@@ -221,8 +221,8 @@ function drawPathDefinitionCharacter(
|
|||||||
const rx = parseFloat(args[0]) * deviceCellWidth;
|
const rx = parseFloat(args[0]) * deviceCellWidth;
|
||||||
const ry = parseFloat(args[1]) * deviceCellHeight;
|
const ry = parseFloat(args[1]) * deviceCellHeight;
|
||||||
const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
|
const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
|
||||||
const largeArcFlag = parseInt(args[3]);
|
const largeArcFlag = parseInt(args[3], 10);
|
||||||
const sweepFlag = parseInt(args[4]);
|
const sweepFlag = parseInt(args[4], 10);
|
||||||
const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
|
const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
|
||||||
const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
|
const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
|
||||||
drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
|
drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
|
||||||
|
|||||||
+2
-2
@@ -23,12 +23,12 @@ declare module '@xterm/addon-webgl' {
|
|||||||
public readonly onChangeTextureAtlas: IEvent<HTMLCanvasElement>;
|
public readonly onChangeTextureAtlas: IEvent<HTMLCanvasElement>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event that is fired when the a new page is added to the texture atlas.
|
* An event that is fired when a new page is added to the texture atlas.
|
||||||
*/
|
*/
|
||||||
public readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
public readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event that is fired when the a page is removed from the texture atlas.
|
* An event that is fired when a page is removed from the texture atlas.
|
||||||
*/
|
*/
|
||||||
public readonly onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
public readonly onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -19,14 +19,17 @@ for (const addon of addons) {
|
|||||||
// Demo job - This requires the others to be built so it's not included when building all
|
// Demo job - This requires the others to be built so it's not included when building all
|
||||||
// jobs.push(createJob('demo-client', [`--demo-client`]));
|
// jobs.push(createJob('demo-client', [`--demo-client`]));
|
||||||
|
|
||||||
await Promise.all(jobs.map((job, i) => {
|
const codes = await Promise.all(jobs.map(job => {
|
||||||
return new Promise(r => {
|
return new Promise(r => {
|
||||||
job.cp.on('exit', code => {
|
job.cp.on('exit', code => {
|
||||||
log(`Finished \x1b[32m${job.name}\x1b[0m${code ? ' \x1b[31mwith errors\x1b[0m' : ''}`);
|
log(`Finished \x1b[32m${job.name}\x1b[0m${code ? ' \x1b[31mwith errors\x1b[0m' : ''}`);
|
||||||
r(code);
|
r(code ?? 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
if (codes.some(code => code !== 0)) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ import { spawnSync } from 'node:child_process';
|
|||||||
import { basename, dirname, resolve, sep } from 'node:path';
|
import { basename, dirname, resolve, sep } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
const nodeModulesPath = resolve(repoRoot, 'node_modules');
|
const nodeModulesPath = resolve(repoRoot, 'node_modules');
|
||||||
const npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ const npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|||||||
|
|
||||||
/** @param {string} message */
|
/** @param {string} message */
|
||||||
function log(message) {
|
function log(message) {
|
||||||
console.info(`[setup-fast] ${message}`);
|
console.info(`[setup-full] ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string[]} args */
|
/** @param {string[]} args */
|
||||||
|
|||||||
+12
-3
@@ -12,7 +12,7 @@ let argv = process.argv.slice(2);
|
|||||||
let suiteFilter = undefined;
|
let suiteFilter = undefined;
|
||||||
while (argv.some(e => e.startsWith('--suite='))) {
|
while (argv.some(e => e.startsWith('--suite='))) {
|
||||||
const i = argv.findIndex(e => e.startsWith('--suite='));
|
const i = argv.findIndex(e => e.startsWith('--suite='));
|
||||||
const match = argv[i].match(/--suite=(?<suitename>.+)/)
|
const match = argv[i].match(/--suite=(?<suitename>.+)/);
|
||||||
suiteFilter = match?.groups?.suitename ?? undefined;
|
suiteFilter = match?.groups?.suitename ?? undefined;
|
||||||
argv.splice(i, 1);
|
argv.splice(i, 1);
|
||||||
}
|
}
|
||||||
@@ -42,12 +42,18 @@ if (suiteFilter) {
|
|||||||
configs = configs.filter(e => e.name === suiteFilter);
|
configs = configs.filter(e => e.name === suiteFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (suiteFilter && configs.length === 0) {
|
||||||
|
console.error(`Unknown suite: ${suiteFilter}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
function npmBinScript(script) {
|
function npmBinScript(script) {
|
||||||
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ?
|
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ?
|
||||||
`${script}.cmd` : script));
|
`${script}.cmd` : script));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
|
let exitCode = 0;
|
||||||
for (const config of configs) {
|
for (const config of configs) {
|
||||||
const command = npmBinScript('playwright');
|
const command = npmBinScript('playwright');
|
||||||
const args = ['test', '-c', config.path, ...argv];
|
const args = ['test', '-c', config.path, ...argv];
|
||||||
@@ -62,10 +68,13 @@ async function run() {
|
|||||||
|
|
||||||
if (run.error) {
|
if (run.error) {
|
||||||
console.error(run.error);
|
console.error(run.error);
|
||||||
process.exit(run.status ?? -1);
|
process.exit(run.status ?? 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(run.status);
|
if (run.status) {
|
||||||
|
exitCode = run.status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
process.exit(exitCode);
|
||||||
}
|
}
|
||||||
run();
|
run();
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ const ENC = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function printMenu() {
|
function printMenu() {
|
||||||
console.log('\x1b[2J\x1b\[HTest mouse reports [Ctrl-C to exit]');
|
console.log('\x1b[2J\x1b[HTest mouse reports [Ctrl-C to exit]');
|
||||||
console.log();
|
console.log();
|
||||||
console.log(' Selected protocol [Ctrl-A to switch]');
|
console.log(' Selected protocol [Ctrl-A to switch]');
|
||||||
const protocols = Object.keys(PROTOCOLS);
|
const protocols = Object.keys(PROTOCOLS);
|
||||||
|
|||||||
+2
-4
@@ -21,7 +21,7 @@ if (process.argv.length > 2) {
|
|||||||
flagArgs = args.filter(e => e.startsWith('--'));
|
flagArgs = args.filter(e => e.startsWith('--'));
|
||||||
// ability to inject particular test files via
|
// ability to inject particular test files via
|
||||||
// npm run test [testFileA testFileB ...]
|
// npm run test [testFileA testFileB ...]
|
||||||
files = args.filter(e => !e.startsWith('--'));
|
const files = args.filter(e => !e.startsWith('--'));
|
||||||
if (files.length) {
|
if (files.length) {
|
||||||
testFiles = files;
|
testFiles = files;
|
||||||
}
|
}
|
||||||
@@ -39,8 +39,6 @@ if (checkCoverage) {
|
|||||||
...testFiles,
|
...testFiles,
|
||||||
...flagArgs
|
...flagArgs
|
||||||
];
|
];
|
||||||
console.info('executable', executable);
|
|
||||||
console.info('args', args);
|
|
||||||
const run = cp.spawnSync(
|
const run = cp.spawnSync(
|
||||||
executable,
|
executable,
|
||||||
args,
|
args,
|
||||||
@@ -51,7 +49,7 @@ if (checkCoverage) {
|
|||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
process.exit(run.status);
|
process.exit(run.status ?? -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const run = cp.spawnSync(
|
const run = cp.spawnSync(
|
||||||
|
|||||||
@@ -11,8 +11,33 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
|||||||
public readonly label = 'webgl';
|
public readonly label = 'webgl';
|
||||||
|
|
||||||
private _textureAtlasContainer!: HTMLElement;
|
private _textureAtlasContainer!: HTMLElement;
|
||||||
|
private _stressRunning = false;
|
||||||
|
private _stressScrolling: HTMLInputElement | undefined;
|
||||||
|
|
||||||
public build(container: HTMLElement): void {
|
public build(container: HTMLElement): void {
|
||||||
|
const stressDiv = document.createElement('div');
|
||||||
|
const stressStart = document.createElement('button');
|
||||||
|
stressStart.id = 'stress-start';
|
||||||
|
stressStart.textContent = 'atlas stress start';
|
||||||
|
stressStart.addEventListener('click', () => this._stress());
|
||||||
|
stressDiv.appendChild(stressStart);
|
||||||
|
|
||||||
|
const stressStop = document.createElement('button');
|
||||||
|
stressStop.id = 'stress-stop';
|
||||||
|
stressStop.textContent = 'atlas stress stop';
|
||||||
|
stressStop.addEventListener('click', () => this._stressRunning = false);
|
||||||
|
stressDiv.appendChild(stressStop);
|
||||||
|
|
||||||
|
this._stressScrolling = document.createElement('input');
|
||||||
|
this._stressScrolling.type = 'checkbox';
|
||||||
|
this._stressScrolling.id = 'stress-scrolling';
|
||||||
|
stressDiv.appendChild(this._stressScrolling);
|
||||||
|
const stressScrollingLabel = document.createElement('label');
|
||||||
|
stressScrollingLabel.htmlFor = 'stress-scrolling';
|
||||||
|
stressScrollingLabel.textContent = 'stress scrolling';
|
||||||
|
stressDiv.appendChild(stressScrollingLabel);
|
||||||
|
container.appendChild(stressDiv);
|
||||||
|
|
||||||
const zoomCheckbox = document.createElement('input');
|
const zoomCheckbox = document.createElement('input');
|
||||||
zoomCheckbox.type = 'checkbox';
|
zoomCheckbox.type = 'checkbox';
|
||||||
zoomCheckbox.id = 'texture-atlas-zoom';
|
zoomCheckbox.id = 'texture-atlas-zoom';
|
||||||
@@ -48,4 +73,33 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
|||||||
canvas.style.width = `${canvas.width / dpr}px`;
|
canvas.style.width = `${canvas.width / dpr}px`;
|
||||||
canvas.style.height = `${canvas.height / dpr}px`;
|
canvas.style.height = `${canvas.height / dpr}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _stress(): Promise<void> {
|
||||||
|
const TEXT = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
|
||||||
|
if (this._stressRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._stressRunning = true;
|
||||||
|
const scrolling = !!(this._stressScrolling?.checked);
|
||||||
|
for (let r = 0; r < 256; r += 2) {
|
||||||
|
for (let g = 0; g < 256; g += 2) {
|
||||||
|
let s: string[] = [];
|
||||||
|
for (let b = 0; b < 256; b += 2) {
|
||||||
|
if (!this._stressRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rbg = `RGB: ${[r, g, b]}`;
|
||||||
|
s.push(`\r\x1b[38;2;${r};${g};${b}m${rbg.padEnd(18, ' ')}${TEXT}`);
|
||||||
|
if (s.length >= 16) {
|
||||||
|
if (scrolling) {
|
||||||
|
await new Promise<void>(r => this._terminal.write(s.join('\r\n') + '\r\n', r));
|
||||||
|
} else {
|
||||||
|
await new Promise<void>(r => this._terminal.write('\x1b[H' + s.join('\r\n'), r));
|
||||||
|
}
|
||||||
|
s = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+5
-36
@@ -6689,16 +6689,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/randombytes": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "^5.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
@@ -6932,27 +6922,6 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/safe-buffer": {
|
|
||||||
"version": "5.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
|
||||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/safer-buffer": {
|
"node_modules/safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
@@ -7078,13 +7047,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serialize-javascript": {
|
"node_modules/serialize-javascript": {
|
||||||
"version": "6.0.2",
|
"version": "7.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz",
|
||||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
"integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"engines": {
|
||||||
"randombytes": "^2.1.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
|
|||||||
+6
-3
@@ -11,6 +11,9 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"addons/*"
|
"addons/*"
|
||||||
],
|
],
|
||||||
|
"overrides": {
|
||||||
|
"serialize-javascript": "^7.0.3"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
"command-line",
|
"command-line",
|
||||||
@@ -61,9 +64,9 @@
|
|||||||
"test-integration-firefox": "node ./bin/test_integration.js --workers=75% \"--project=FirefoxStable\"",
|
"test-integration-firefox": "node ./bin/test_integration.js --workers=75% \"--project=FirefoxStable\"",
|
||||||
"test-integration-webkit": "node ./bin/test_integration.js --workers=75% \"--project=WebKit\"",
|
"test-integration-webkit": "node ./bin/test_integration.js --workers=75% \"--project=WebKit\"",
|
||||||
"test-integration-debug": "node ./bin/test_integration.js --workers=1 --headed --timeout=30000",
|
"test-integration-debug": "node ./bin/test_integration.js --workers=1 --headed --timeout=30000",
|
||||||
"benchmark": "xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
|
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
|
||||||
"benchmark-baseline": "xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/*benchmark.js",
|
"benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/*benchmark.js",
|
||||||
"benchmark-eval": "xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/*benchmark.js",
|
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/*benchmark.js",
|
||||||
"clean": "rm -rf lib out addons/*/lib addons/*/out",
|
"clean": "rm -rf lib out addons/*/lib addons/*/out",
|
||||||
"vtfeatures": "node bin/extract_vtfeatures.js src/*/*.ts src/*/*/*.ts src/*.ts addons/**/src/*.ts",
|
"vtfeatures": "node bin/extract_vtfeatures.js src/*/*.ts src/*/*/*.ts src/*.ts addons/**/src/*.ts",
|
||||||
"prepackage": "npm run build",
|
"prepackage": "npm run build",
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|||||||
private _keyDownHandled: boolean = false;
|
private _keyDownHandled: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records whether a keydown event has occured since the last keyup event, i.e. whether a key
|
* Records whether a keydown event has occurred since the last keyup event, i.e. whether a key
|
||||||
* is currently "pressed".
|
* is currently "pressed".
|
||||||
*/
|
*/
|
||||||
private _keyDownSeen: boolean = false;
|
private _keyDownSeen: boolean = false;
|
||||||
@@ -505,7 +505,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|||||||
this.textarea,
|
this.textarea,
|
||||||
parent.ownerDocument.defaultView ?? window,
|
parent.ownerDocument.defaultView ?? window,
|
||||||
// Force unsafe null in node.js environment for tests
|
// Force unsafe null in node.js environment for tests
|
||||||
this._document ?? (typeof window !== 'undefined') ? window.document : null as any
|
this._document ?? ((typeof window !== 'undefined') ? window.document : null as any)
|
||||||
));
|
));
|
||||||
this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
|
this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
|
||||||
|
|
||||||
@@ -1072,10 +1072,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|||||||
* Clear the entire buffer, making the prompt line the new first line.
|
* Clear the entire buffer, making the prompt line the new first line.
|
||||||
*/
|
*/
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
if (this.buffer.ybase === 0 && this.buffer.y === 0) {
|
|
||||||
// Don't clear if it's already clear
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.buffer.clearAllMarkers();
|
this.buffer.clearAllMarkers();
|
||||||
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
|
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
|
||||||
this.buffer.lines.length = 1;
|
this.buffer.lines.length = 1;
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { OscLinkProvider } from './OscLinkProvider';
|
|||||||
import { ILink } from './Types';
|
import { ILink } from './Types';
|
||||||
import { createCellData, MockBufferService, MockOptionsService } from '../common/TestUtils.test';
|
import { createCellData, MockBufferService, MockOptionsService } from '../common/TestUtils.test';
|
||||||
import { IBufferService, IOscLinkService } from '../common/services/Services';
|
import { IBufferService, IOscLinkService } from '../common/services/Services';
|
||||||
import { IBufferLine, IOscLinkData } from '../common/Types';
|
import { IOscLinkData } from '../common/Types';
|
||||||
|
import { IBufferLine } from '../common/buffer/Types';
|
||||||
|
|
||||||
class TestOscLinkService implements IOscLinkService {
|
class TestOscLinkService implements IOscLinkService {
|
||||||
public serviceBrand: any;
|
public serviceBrand: any;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { IBufferRange, ILink } from './Types';
|
import { IBufferRange, ILink } from './Types';
|
||||||
import { ILinkProvider } from './services/Services';
|
import { ILinkProvider } from './services/Services';
|
||||||
import { CellData } from '../common/buffer/CellData';
|
import { CellData } from '../common/buffer/CellData';
|
||||||
import { IBufferLine } from '../common/Types';
|
import { IBufferLine } from '../common/buffer/Types';
|
||||||
import { IBufferService, IOptionsService, IOscLinkService } from '../common/services/Services';
|
import { IBufferService, IOptionsService, IOscLinkService } from '../common/services/Services';
|
||||||
|
|
||||||
export class OscLinkProvider implements ILinkProvider {
|
export class OscLinkProvider implements ILinkProvider {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
if (this._animationFrame) {
|
if (this._animationFrame !== undefined) {
|
||||||
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
||||||
this._animationFrame = undefined;
|
this._animationFrame = undefined;
|
||||||
}
|
}
|
||||||
@@ -31,9 +31,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
|||||||
|
|
||||||
public addRefreshCallback(callback: FrameRequestCallback): number {
|
public addRefreshCallback(callback: FrameRequestCallback): number {
|
||||||
this._refreshCallbacks.push(callback);
|
this._refreshCallbacks.push(callback);
|
||||||
if (!this._animationFrame) {
|
this._animationFrame ??= this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());
|
||||||
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());
|
|
||||||
}
|
|
||||||
return this._animationFrame;
|
return this._animationFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
|||||||
this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
|
this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
|
||||||
this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
|
this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
|
||||||
|
|
||||||
if (this._animationFrame) {
|
if (this._animationFrame !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { assert } from 'chai';
|
|||||||
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
|
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
|
||||||
import { CellData } from '../common/buffer/CellData';
|
import { CellData } from '../common/buffer/CellData';
|
||||||
import { MockUnicodeService, createCellData } from '../common/TestUtils.test';
|
import { MockUnicodeService, createCellData } from '../common/TestUtils.test';
|
||||||
import { IMarker } from '../common/Types';
|
import { IMarker } from '../common/buffer/Types';
|
||||||
|
|
||||||
const INIT_COLS = 80;
|
const INIT_COLS = 80;
|
||||||
const INIT_ROWS = 24;
|
const INIT_ROWS = 24;
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration, I
|
|||||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
|
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
|
||||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from './renderer/shared/Types';
|
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from './renderer/shared/Types';
|
||||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from './Types';
|
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from './Types';
|
||||||
import { IBuffer, IBufferSet } from '../common/buffer/Types';
|
import { IAttributeData, IBuffer, IBufferLine, IBufferSet, ICellData } from '../common/buffer/Types';
|
||||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from '../common/Types';
|
import { ICircularList } from '../common/CircularList';
|
||||||
|
import { XtermListener, ICharset, ITerminalOptions, ColorIndex } from '../common/Types';
|
||||||
import { Buffer } from '../common/buffer/Buffer';
|
import { Buffer } from '../common/buffer/Buffer';
|
||||||
import * as Browser from '../common/Platform';
|
import * as Browser from '../common/Platform';
|
||||||
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
|
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
|
|
||||||
|
|
||||||
import { IRenderDebouncer } from './Types';
|
import { IRenderDebouncer } from './Types';
|
||||||
|
|
||||||
|
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounces calls to update screen readers to update at most once configurable interval of time.
|
* Debounces calls to update screen readers to update at most once configurable interval of time.
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +31,9 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
|
|||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
if (this._refreshTimeoutID) {
|
if (this._refreshTimeoutID) {
|
||||||
clearTimeout(this._refreshTimeoutID);
|
clearTimeout(this._refreshTimeoutID);
|
||||||
|
this._refreshTimeoutID = undefined;
|
||||||
}
|
}
|
||||||
|
this._additionalRefreshRequested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
|
public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
|
||||||
@@ -47,7 +49,12 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
|
|||||||
// enough time to pass before refreshing again.
|
// enough time to pass before refreshing again.
|
||||||
const refreshRequestTime: number = performance.now();
|
const refreshRequestTime: number = performance.now();
|
||||||
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
|
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
|
||||||
// Enough time has lapsed since the last refresh; refresh immediately
|
// Enough time has elapsed since the last refresh; refresh immediately
|
||||||
|
if (this._refreshTimeoutID !== undefined) {
|
||||||
|
clearTimeout(this._refreshTimeoutID);
|
||||||
|
this._refreshTimeoutID = undefined;
|
||||||
|
this._additionalRefreshRequested = false;
|
||||||
|
}
|
||||||
this._lastRefreshMs = refreshRequestTime;
|
this._lastRefreshMs = refreshRequestTime;
|
||||||
this._innerRefresh();
|
this._innerRefresh();
|
||||||
} else if (!this._additionalRefreshRequested) {
|
} else if (!this._additionalRefreshRequested) {
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CharData, IColor, ICoreTerminal, ITerminalOptions } from '../common/Types';
|
import { IColor, ITerminalOptions } from '../common/Types';
|
||||||
import { IBuffer } from '../common/buffer/Types';
|
import { CharData, IBuffer } from '../common/buffer/Types';
|
||||||
|
import { ICoreTerminal } from '../common/CoreTerminal';
|
||||||
import { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm';
|
import { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm';
|
||||||
import { channels, css } from '../common/Color';
|
import { channels, css } from '../common/Color';
|
||||||
import type { IEvent } from '../common/Event';
|
import type { IEvent } from '../common/Event';
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
|
export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
const elementStyle = window.getComputedStyle(element);
|
const elementStyle = window.getComputedStyle(element);
|
||||||
const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'));
|
const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'), 10);
|
||||||
const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'));
|
const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'), 10);
|
||||||
return [
|
return [
|
||||||
event.clientX - rect.left - leftPadding,
|
event.clientX - rect.left - leftPadding,
|
||||||
event.clientY - rect.top - topPadding
|
event.clientY - rect.top - topPadding
|
||||||
@@ -22,7 +22,7 @@ export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyl
|
|||||||
* @param event The mouse event.
|
* @param event The mouse event.
|
||||||
* @param element The terminal's container element.
|
* @param element The terminal's container element.
|
||||||
* @param colCount The number of columns in the terminal.
|
* @param colCount The number of columns in the terminal.
|
||||||
* @param rowCount The number of rows n the terminal.
|
* @param rowCount The number of rows in the terminal.
|
||||||
* @param hasValidCharSize Whether there is a valid character size available.
|
* @param hasValidCharSize Whether there is a valid character size available.
|
||||||
* @param cssCellWidth The cell width device pixel render dimensions.
|
* @param cssCellWidth The cell width device pixel render dimensions.
|
||||||
* @param cssCellHeight The cell height device pixel render dimensions.
|
* @param cssCellHeight The cell height device pixel render dimensions.
|
||||||
@@ -31,21 +31,17 @@ export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyl
|
|||||||
* select that cell and the right half will select the next cell.
|
* select that cell and the right half will select the next cell.
|
||||||
*/
|
*/
|
||||||
export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: Pick<MouseEvent, 'clientX' | 'clientY'>, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, cssCellWidth: number, cssCellHeight: number, isSelection?: boolean): [number, number] | undefined {
|
export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: Pick<MouseEvent, 'clientX' | 'clientY'>, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, cssCellWidth: number, cssCellHeight: number, isSelection?: boolean): [number, number] | undefined {
|
||||||
// Coordinates cannot be measured if there are no valid
|
// Coordinates cannot be measured if there is no valid character size.
|
||||||
if (!hasValidCharSize) {
|
if (!hasValidCharSize) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coords = getCoordsRelativeToElement(window, event, element);
|
const coords = getCoordsRelativeToElement(window, event, element);
|
||||||
if (!coords) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
coords[0] = Math.ceil((coords[0] + (isSelection ? cssCellWidth / 2 : 0)) / cssCellWidth);
|
coords[0] = Math.ceil((coords[0] + (isSelection ? cssCellWidth / 2 : 0)) / cssCellWidth);
|
||||||
coords[1] = Math.ceil(coords[1] / cssCellHeight);
|
coords[1] = Math.ceil(coords[1] / cssCellHeight);
|
||||||
|
|
||||||
// Ensure coordinates are within the terminal viewport. Note that selections
|
// Ensure coordinates are within the terminal viewport. Note that selections
|
||||||
// need an addition point of precision to cover the end point (as characters
|
// need an additional point of precision to cover the end point (as characters
|
||||||
// cover half of one char and half of the next).
|
// cover half of one char and half of the next).
|
||||||
coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
|
coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
|
||||||
coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
|
coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ function wrappedRowsForRow(currentRow: number, bufferService: IBufferService): n
|
|||||||
*/
|
*/
|
||||||
function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
|
function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
|
||||||
let startRow;
|
let startRow;
|
||||||
if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {
|
if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {
|
||||||
startRow = targetY - wrappedRowsForRow(targetY, bufferService);
|
startRow = targetY - wrappedRowsForRow(targetY, bufferService);
|
||||||
} else {
|
} else {
|
||||||
startRow = startY;
|
startRow = startY;
|
||||||
|
|||||||
@@ -536,9 +536,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
for (let y = start; y <= end; y++) {
|
for (let y = start; y <= end; y++) {
|
||||||
const row = y + buffer.ydisp;
|
const row = y + buffer.ydisp;
|
||||||
const rowElement = this._rowElements[y];
|
const rowElement = this._rowElements[y];
|
||||||
|
if (!rowElement) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const lineData = buffer.lines.get(row);
|
const lineData = buffer.lines.get(row);
|
||||||
if (!rowElement || !lineData) {
|
if (!lineData) {
|
||||||
break;
|
rowElement.replaceChildren();
|
||||||
|
this._setRowBlinkState(y, false);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
rowElement.replaceChildren(
|
rowElement.replaceChildren(
|
||||||
...this._rowFactory.createRow(
|
...this._rowFactory.createRow(
|
||||||
@@ -610,9 +615,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
for (let i = y; i <= y2; ++i) {
|
for (let i = y; i <= y2; ++i) {
|
||||||
const row = i + buffer.ydisp;
|
const row = i + buffer.ydisp;
|
||||||
const rowElement = this._rowElements[i];
|
const rowElement = this._rowElements[i];
|
||||||
|
if (!rowElement) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const bufferline = buffer.lines.get(row);
|
const bufferline = buffer.lines.get(row);
|
||||||
if (!rowElement || !bufferline) {
|
if (!bufferline) {
|
||||||
break;
|
rowElement.replaceChildren();
|
||||||
|
this._setRowBlinkState(i, false);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
rowElement.replaceChildren(
|
rowElement.replaceChildren(
|
||||||
...this._rowFactory.createRow(
|
...this._rowFactory.createRow(
|
||||||
@@ -694,7 +704,7 @@ class CursorBlinkStateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _clearIdleTimer(): void {
|
private _clearIdleTimer(): void {
|
||||||
if (this._idleTimeout) {
|
if (this._idleTimeout !== undefined) {
|
||||||
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
|
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
|
||||||
this._idleTimeout = undefined;
|
this._idleTimeout = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { DomRendererRowFactory } from './DomRendererRowFactory';
|
|||||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from '../../../common/buffer/Constants';
|
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from '../../../common/buffer/Constants';
|
||||||
import { BufferLine, DEFAULT_ATTR_DATA } from '../../../common/buffer/BufferLine';
|
import { BufferLine, DEFAULT_ATTR_DATA } from '../../../common/buffer/BufferLine';
|
||||||
import { BufferLineStringCache } from '../../../common/buffer/BufferLineStringCache';
|
import { BufferLineStringCache } from '../../../common/buffer/BufferLineStringCache';
|
||||||
import { IBufferLine } from '../../../common/Types';
|
import { IBufferLine } from '../../../common/buffer/Types';
|
||||||
import { CellData } from '../../../common/buffer/CellData';
|
import { CellData } from '../../../common/buffer/CellData';
|
||||||
import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from '../../../common/TestUtils.test';
|
import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from '../../../common/TestUtils.test';
|
||||||
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from '../../TestUtils.test';
|
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from '../../TestUtils.test';
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IBufferLine, ICellData, IColor } from '../../../common/Types';
|
import { IColor } from '../../../common/Types';
|
||||||
|
import { IBufferLine, ICellData } from '../../../common/buffer/Types';
|
||||||
import { INVERTED_DEFAULT_COLOR } from '../shared/Constants';
|
import { INVERTED_DEFAULT_COLOR } from '../shared/Constants';
|
||||||
import { WHITESPACE_CELL_CHAR, Attributes } from '../../../common/buffer/Constants';
|
import { WHITESPACE_CELL_CHAR, Attributes } from '../../../common/buffer/Constants';
|
||||||
import { CellData } from '../../../common/buffer/CellData';
|
import { CellData } from '../../../common/buffer/CellData';
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export class StandardWheelEvent {
|
|||||||
let shouldFactorDPR: boolean = false;
|
let shouldFactorDPR: boolean = false;
|
||||||
if (platform.isChrome) {
|
if (platform.isChrome) {
|
||||||
const chromeVersionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
|
const chromeVersionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
|
||||||
const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1]) : 123;
|
const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1], 10) : 123;
|
||||||
shouldFactorDPR = chromeMajorVersion <= 122;
|
shouldFactorDPR = chromeMajorVersion <= 122;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ describe('SelectionModel', () => {
|
|||||||
assert.deepEqual(model.finalSelectionStart, undefined);
|
assert.deepEqual(model.finalSelectionStart, undefined);
|
||||||
assert.deepEqual(model.finalSelectionEnd, undefined);
|
assert.deepEqual(model.finalSelectionEnd, undefined);
|
||||||
});
|
});
|
||||||
|
it('should reset selection start to origin when start row is trimmed', () => {
|
||||||
|
model.selectionStart = [50, 0];
|
||||||
|
model.selectionEnd = [10, 2];
|
||||||
|
assert.equal(model.handleTrim(1), true);
|
||||||
|
assert.deepEqual(model.finalSelectionStart, [0, 0]);
|
||||||
|
assert.deepEqual(model.finalSelectionEnd, [10, 1]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('finalSelectionStart', () => {
|
describe('finalSelectionStart', () => {
|
||||||
|
|||||||
@@ -135,9 +135,10 @@ export class SelectionModel {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the selection start is trimmed, ensure the start column is 0.
|
// If the selection start row is trimmed away, reset to the buffer origin.
|
||||||
if (this.selectionStart && this.selectionStart[1] < 0) {
|
if (this.selectionStart && this.selectionStart[1] < 0) {
|
||||||
this.selectionStart[1] = 0;
|
this.selectionStart = [0, 0];
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ICharacterJoinerService } from './Services';
|
|||||||
import { CharacterJoinerService } from './CharacterJoinerService';
|
import { CharacterJoinerService } from './CharacterJoinerService';
|
||||||
import { BufferLine } from '../../common/buffer/BufferLine';
|
import { BufferLine } from '../../common/buffer/BufferLine';
|
||||||
import { BufferLineStringCache } from '../../common/buffer/BufferLineStringCache';
|
import { BufferLineStringCache } from '../../common/buffer/BufferLineStringCache';
|
||||||
import { IBufferLine } from '../../common/Types';
|
import { IBufferLine } from '../../common/buffer/Types';
|
||||||
import { CellData } from '../../common/buffer/CellData';
|
import { CellData } from '../../common/buffer/CellData';
|
||||||
import { MockBufferService, createCellData } from '../../common/TestUtils.test';
|
import { MockBufferService, createCellData } from '../../common/TestUtils.test';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IBufferLine, ICellData, CharData } from '../../common/Types';
|
import { CharData, IBufferLine, ICellData } from '../../common/buffer/Types';
|
||||||
import { ICharacterJoiner } from '../Types';
|
import { ICharacterJoiner } from '../Types';
|
||||||
import { AttributeData } from '../../common/buffer/AttributeData';
|
import { AttributeData } from '../../common/buffer/AttributeData';
|
||||||
import { WHITESPACE_CELL_CHAR, Content } from '../../common/buffer/Constants';
|
import { WHITESPACE_CELL_CHAR, Content } from '../../common/buffer/Constants';
|
||||||
@@ -100,6 +100,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
|||||||
|
|
||||||
const ranges: [number, number][] = [];
|
const ranges: [number, number][] = [];
|
||||||
const lineStr = line.translateToString(true);
|
const lineStr = line.translateToString(true);
|
||||||
|
const trimmedLength = line.getTrimmedLength();
|
||||||
|
|
||||||
// Because some cells can be represented by multiple javascript characters,
|
// Because some cells can be represented by multiple javascript characters,
|
||||||
// we track the cell and the string indexes separately. This allows us to
|
// we track the cell and the string indexes separately. This allows us to
|
||||||
@@ -111,7 +112,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
|||||||
let rangeAttrFG = line.getFg(0);
|
let rangeAttrFG = line.getFg(0);
|
||||||
let rangeAttrBG = line.getBg(0);
|
let rangeAttrBG = line.getBg(0);
|
||||||
|
|
||||||
for (let x = 0; x < line.getTrimmedLength(); x++) {
|
for (let x = 0; x < trimmedLength; x++) {
|
||||||
line.loadCell(x, this._workCell);
|
line.loadCell(x, this._workCell);
|
||||||
|
|
||||||
if (this._workCell.getWidth() === 0) {
|
if (this._workCell.getWidth() === 0) {
|
||||||
@@ -147,7 +148,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process any trailing ranges.
|
// Process any trailing ranges.
|
||||||
if (this._bufferService.cols - rangeStartColumn > 1) {
|
if (trimmedLength - rangeStartColumn > 1) {
|
||||||
const joinedRanges = this._getJoinedRanges(
|
const joinedRanges = this._getJoinedRanges(
|
||||||
lineStr,
|
lineStr,
|
||||||
rangeStartStringIndex,
|
rangeStartStringIndex,
|
||||||
@@ -216,7 +217,8 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let x = startCol; x < this._bufferService.cols; x++) {
|
const trimmedLength = line.getTrimmedLength();
|
||||||
|
for (let x = startCol; x < trimmedLength; x++) {
|
||||||
const width = line.getWidth(x);
|
const width = line.getWidth(x);
|
||||||
const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
|
const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
|
||||||
|
|
||||||
@@ -264,7 +266,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
|||||||
// If there is still a range left at the end, it must extend all the way to
|
// If there is still a range left at the end, it must extend all the way to
|
||||||
// the end of the line.
|
// the end of the line.
|
||||||
if (currentRange) {
|
if (currentRange) {
|
||||||
currentRange[1] = this._bufferService.cols;
|
currentRange[1] = trimmedLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ describe('MouseService _triggerMouseEvent', () => {
|
|||||||
for (let i = 0; i < bufferService.cols; ++i) {
|
for (let i = 0; i < bufferService.cols; ++i) {
|
||||||
assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||||
if (i > 222) {
|
if (i > 222) {
|
||||||
// supress mouse reports if we are out of addressible range (max. 222)
|
// suppress mouse reports if we are out of addressable range (max. 222)
|
||||||
assert.deepEqual(toBytes(reports.pop()), []);
|
assert.deepEqual(toBytes(reports.pop()), []);
|
||||||
} else {
|
} else {
|
||||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { SelectionService, SelectionMode } from './SelectionService';
|
import { SelectionService, SelectionMode } from './SelectionService';
|
||||||
import { SelectionModel } from '../selection/SelectionModel';
|
import { SelectionModel } from '../selection/SelectionModel';
|
||||||
import { IBufferLine } from '../../common/Types';
|
import { IBuffer, IBufferLine } from '../../common/buffer/Types';
|
||||||
import { MockBufferService, MockOptionsService, MockCoreService, MockMouseStateService, createCellData } from '../../common/TestUtils.test';
|
import { MockBufferService, MockOptionsService, MockCoreService, MockMouseStateService, createCellData } from '../../common/TestUtils.test';
|
||||||
import { BufferLine } from '../../common/buffer/BufferLine';
|
import { BufferLine } from '../../common/buffer/BufferLine';
|
||||||
import { BufferLineStringCache } from '../../common/buffer/BufferLineStringCache';
|
import { BufferLineStringCache } from '../../common/buffer/BufferLineStringCache';
|
||||||
import { IBufferService, IOptionsService } from '../../common/services/Services';
|
import { IBufferService, IOptionsService } from '../../common/services/Services';
|
||||||
import { MockCoreBrowserService, MockMouseService, MockRenderService } from '../TestUtils.test';
|
import { MockCoreBrowserService, MockMouseService, MockRenderService } from '../TestUtils.test';
|
||||||
import { CellData } from '../../common/buffer/CellData';
|
import { CellData } from '../../common/buffer/CellData';
|
||||||
import { IBuffer } from '../../common/buffer/Types';
|
|
||||||
import { IRenderService } from './Services';
|
import { IRenderService } from './Services';
|
||||||
|
|
||||||
const TEST_STRING_CACHE = new BufferLineStringCache();
|
const TEST_STRING_CACHE = new BufferLineStringCache();
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from
|
|||||||
import { ICoreBrowserService, IMouseCoordsService, IRenderService, ISelectionService } from './Services';
|
import { ICoreBrowserService, IMouseCoordsService, IRenderService, ISelectionService } from './Services';
|
||||||
import { Disposable, MutableDisposable, toDisposable } from '../../common/Lifecycle';
|
import { Disposable, MutableDisposable, toDisposable } from '../../common/Lifecycle';
|
||||||
import * as Browser from '../../common/Platform';
|
import * as Browser from '../../common/Platform';
|
||||||
import { IBufferLine, ICellData, IDisposable } from '../../common/Types';
|
import { IDisposable } from '../../common/Types';
|
||||||
|
import { IBuffer, IBufferLine, ICellData } from '../../common/buffer/Types';
|
||||||
import { getRangeLength } from '../../common/buffer/BufferRange';
|
import { getRangeLength } from '../../common/buffer/BufferRange';
|
||||||
import { CellData } from '../../common/buffer/CellData';
|
import { CellData } from '../../common/buffer/CellData';
|
||||||
import { IBuffer } from '../../common/buffer/Types';
|
|
||||||
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from '../../common/services/Services';
|
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from '../../common/services/Services';
|
||||||
import { Emitter } from '../../common/Event';
|
import { Emitter } from '../../common/Event';
|
||||||
|
|
||||||
@@ -704,7 +704,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
|||||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
||||||
this._model.selectionEnd[0] = this._bufferService.cols;
|
this._model.selectionEnd[0] = this._bufferService.cols;
|
||||||
}
|
}
|
||||||
this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1);
|
this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows - 1, buffer.lines.length - 1);
|
||||||
} else {
|
} else {
|
||||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
||||||
this._model.selectionEnd[0] = 0;
|
this._model.selectionEnd[0] = 0;
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export class ThemeService extends Disposable implements IThemeService {
|
|||||||
colors.ansi[i + 16] = parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]);
|
colors.ansi[i + 16] = parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear our the cache
|
// Clear the cache
|
||||||
this._contrastCache.clear();
|
this._contrastCache.clear();
|
||||||
this._halfContrastCache.clear();
|
this._halfContrastCache.clear();
|
||||||
this._updateRestoreColors();
|
this._updateRestoreColors();
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ICircularList } from './Types';
|
|
||||||
import { Disposable } from './Lifecycle';
|
import { Disposable } from './Lifecycle';
|
||||||
import { Emitter } from './Event';
|
import { Emitter, type IEvent } from './Event';
|
||||||
|
|
||||||
export interface IInsertEvent {
|
export interface IInsertEvent {
|
||||||
index: number;
|
index: number;
|
||||||
@@ -17,6 +16,28 @@ export interface IDeleteEvent {
|
|||||||
amount: number;
|
amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICircularList<T> {
|
||||||
|
length: number;
|
||||||
|
maxLength: number;
|
||||||
|
isFull: boolean;
|
||||||
|
|
||||||
|
onDeleteEmitter: Emitter<IDeleteEvent>;
|
||||||
|
onDelete: IEvent<IDeleteEvent>;
|
||||||
|
onInsertEmitter: Emitter<IInsertEvent>;
|
||||||
|
onInsert: IEvent<IInsertEvent>;
|
||||||
|
onTrimEmitter: Emitter<number>;
|
||||||
|
onTrim: IEvent<number>;
|
||||||
|
|
||||||
|
get(index: number): T | undefined;
|
||||||
|
set(index: number, value: T): void;
|
||||||
|
push(value: T): void;
|
||||||
|
recycle(): T;
|
||||||
|
pop(): T | undefined;
|
||||||
|
splice(start: number, deleteCount: number, ...items: T[]): void;
|
||||||
|
trimStart(count: number): void;
|
||||||
|
shiftElements(start: number, count: number, offset: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a circular list; a list with a maximum size that wraps around when push is called,
|
* Represents a circular list; a list with a maximum size that wraps around when push is called,
|
||||||
* overriding values at the start of the list.
|
* overriding values at the start of the list.
|
||||||
|
|||||||
+4
-4
@@ -177,9 +177,9 @@ export namespace css {
|
|||||||
// Formats: rgb() or rgba()
|
// Formats: rgb() or rgba()
|
||||||
const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);
|
const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);
|
||||||
if (rgbaMatch) {
|
if (rgbaMatch) {
|
||||||
$r = parseInt(rgbaMatch[1]);
|
$r = parseInt(rgbaMatch[1], 10);
|
||||||
$g = parseInt(rgbaMatch[2]);
|
$g = parseInt(rgbaMatch[2], 10);
|
||||||
$b = parseInt(rgbaMatch[3]);
|
$b = parseInt(rgbaMatch[3], 10);
|
||||||
$a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
|
$a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
|
||||||
return channels.toColor($r, $g, $b, $a);
|
return channels.toColor($r, $g, $b, $a);
|
||||||
}
|
}
|
||||||
@@ -373,7 +373,7 @@ export function toPaddedHex(c: number): string {
|
|||||||
/**
|
/**
|
||||||
* Gets the contrast ratio between two relative luminance values.
|
* Gets the contrast ratio between two relative luminance values.
|
||||||
* @param l1 The first relative luminance.
|
* @param l1 The first relative luminance.
|
||||||
* @param l2 The first relative luminance.
|
* @param l2 The second relative luminance.
|
||||||
* @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
* @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||||
*/
|
*/
|
||||||
export function contrastRatio(l1: number, l2: number): number {
|
export function contrastRatio(l1: number, l2: number): number {
|
||||||
|
|||||||
@@ -21,19 +21,20 @@
|
|||||||
* http://linux.die.net/man/7/urxvt
|
* http://linux.die.net/man/7/urxvt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from './services/Services';
|
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, IOscLinkService } from './services/Services';
|
||||||
import { InstantiationService } from './services/InstantiationService';
|
import { InstantiationService } from './services/InstantiationService';
|
||||||
import { LogService } from './services/LogService';
|
import { LogService } from './services/LogService';
|
||||||
import { BufferService, BufferServiceConstants } from './services/BufferService';
|
import { BufferService, BufferServiceConstants } from './services/BufferService';
|
||||||
import { OptionsService } from './services/OptionsService';
|
import { OptionsService } from './services/OptionsService';
|
||||||
import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent } from './Types';
|
import { IDisposable, IScrollEvent, ITerminalOptions, IParams } from './Types';
|
||||||
|
import { IAttributeData, IBufferSet } from './buffer/Types';
|
||||||
import { CoreService } from './services/CoreService';
|
import { CoreService } from './services/CoreService';
|
||||||
import { MouseStateService } from './services/MouseStateService';
|
import { MouseStateService } from './services/MouseStateService';
|
||||||
|
import { UnicodeV6 } from './input/UnicodeV6';
|
||||||
import { UnicodeService } from './services/UnicodeService';
|
import { UnicodeService } from './services/UnicodeService';
|
||||||
import { CharsetService } from './services/CharsetService';
|
import { CharsetService } from './services/CharsetService';
|
||||||
import { updateWindowsModeWrappedState } from './WindowsMode';
|
import { updateWindowsModeWrappedState } from './WindowsMode';
|
||||||
import { IFunctionIdentifier, IParams } from './parser/Types';
|
import { IFunctionIdentifier } from './parser/Types';
|
||||||
import { IBufferSet } from './buffer/Types';
|
|
||||||
import { InputHandler } from './InputHandler';
|
import { InputHandler } from './InputHandler';
|
||||||
import { WriteBuffer } from './input/WriteBuffer';
|
import { WriteBuffer } from './input/WriteBuffer';
|
||||||
import { OscLinkService } from './services/OscLinkService';
|
import { OscLinkService } from './services/OscLinkService';
|
||||||
@@ -43,6 +44,20 @@ import { Disposable, MutableDisposable, toDisposable } from './Lifecycle';
|
|||||||
// Only trigger this warning a single time per session
|
// Only trigger this warning a single time per session
|
||||||
let hasWriteSyncWarnHappened = false;
|
let hasWriteSyncWarnHappened = false;
|
||||||
|
|
||||||
|
export interface ICoreTerminal {
|
||||||
|
mouseStateService: IMouseStateService;
|
||||||
|
coreService: ICoreService;
|
||||||
|
optionsService: IOptionsService;
|
||||||
|
unicodeService: IUnicodeService;
|
||||||
|
buffers: IBufferSet;
|
||||||
|
options: Required<ITerminalOptions>;
|
||||||
|
registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
|
||||||
|
registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
|
||||||
|
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
|
||||||
|
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
||||||
|
registerApcHandler(id: IFunctionIdentifier, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||||
protected readonly _instantiationService: IInstantiationService;
|
protected readonly _instantiationService: IInstantiationService;
|
||||||
protected readonly _bufferService: IBufferService;
|
protected readonly _bufferService: IBufferService;
|
||||||
@@ -116,6 +131,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
|||||||
this.mouseStateService = this._register(this._instantiationService.createInstance(MouseStateService));
|
this.mouseStateService = this._register(this._instantiationService.createInstance(MouseStateService));
|
||||||
this._instantiationService.setService(IMouseStateService, this.mouseStateService);
|
this._instantiationService.setService(IMouseStateService, this.mouseStateService);
|
||||||
this.unicodeService = this._register(this._instantiationService.createInstance(UnicodeService));
|
this.unicodeService = this._register(this._instantiationService.createInstance(UnicodeService));
|
||||||
|
this.unicodeService.register(new UnicodeV6());
|
||||||
this._instantiationService.setService(IUnicodeService, this.unicodeService);
|
this._instantiationService.setService(IUnicodeService, this.unicodeService);
|
||||||
this._charsetService = this._instantiationService.createInstance(CharsetService);
|
this._charsetService = this._instantiationService.createInstance(CharsetService);
|
||||||
this._instantiationService.setService(ICharsetService, this._charsetService);
|
this._instantiationService.setService(ICharsetService, this._charsetService);
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { InputHandler } from './InputHandler';
|
import { InputHandler } from './InputHandler';
|
||||||
import { IBufferLine, IAttributeData, IColorEvent, ColorRequestType, SpecialColorIndex } from './Types';
|
import { IColorEvent, ColorRequestType, SpecialColorIndex } from './Types';
|
||||||
|
import { IAttributeData, IBufferLine } from './buffer/Types';
|
||||||
import { DEFAULT_ATTR_DATA } from './buffer/BufferLine';
|
import { DEFAULT_ATTR_DATA } from './buffer/BufferLine';
|
||||||
import { CellData } from './buffer/CellData';
|
import { CellData } from './buffer/CellData';
|
||||||
import { Attributes, BgFlags, UnderlineStyle } from './buffer/Constants';
|
import { Attributes, BgFlags, UnderlineStyle } from './buffer/Constants';
|
||||||
@@ -15,8 +16,10 @@ import { MockCoreService, MockBufferService, MockOptionsService, MockLogService,
|
|||||||
import { IBufferService, ICoreService, type IOscLinkService } from './services/Services';
|
import { IBufferService, ICoreService, type IOscLinkService } from './services/Services';
|
||||||
import { DEFAULT_OPTIONS } from './services/OptionsService';
|
import { DEFAULT_OPTIONS } from './services/OptionsService';
|
||||||
import { BufferService } from './services/BufferService';
|
import { BufferService } from './services/BufferService';
|
||||||
|
import { CharsetService } from './services/CharsetService';
|
||||||
import { CoreService } from './services/CoreService';
|
import { CoreService } from './services/CoreService';
|
||||||
import { OscLinkService } from './services/OscLinkService';
|
import { OscLinkService } from './services/OscLinkService';
|
||||||
|
import { CHARSETS } from './data/Charsets';
|
||||||
|
|
||||||
|
|
||||||
function getCursor(bufferService: IBufferService): number[] {
|
function getCursor(bufferService: IBufferService): number[] {
|
||||||
@@ -638,19 +641,38 @@ describe('InputHandler', () => {
|
|||||||
});
|
});
|
||||||
describe('print', () => {
|
describe('print', () => {
|
||||||
it('should not cause an infinite loop (regression test)', () => {
|
it('should not cause an infinite loop (regression test)', () => {
|
||||||
const inputHandler = new TestInputHandler(
|
|
||||||
new MockBufferService(80, 30),
|
|
||||||
new MockCharsetService(),
|
|
||||||
new MockCoreService(),
|
|
||||||
new MockLogService(),
|
|
||||||
new MockOptionsService(),
|
|
||||||
new MockOscLinkService(),
|
|
||||||
new MockMouseStateService(),
|
|
||||||
new MockUnicodeService()
|
|
||||||
);
|
|
||||||
const container = new Uint32Array(10);
|
const container = new Uint32Array(10);
|
||||||
container[0] = 0x200B;
|
container[0] = 0x200B;
|
||||||
|
const lineCountBefore = bufferService.buffer.lines.length;
|
||||||
inputHandler.print(container, 0, 1);
|
inputHandler.print(container, 0, 1);
|
||||||
|
assert.strictEqual(bufferService.buffer.y, 0);
|
||||||
|
assert.strictEqual(bufferService.buffer.lines.length, lineCountBefore);
|
||||||
|
});
|
||||||
|
it('should join combining characters in a single print', async () => {
|
||||||
|
await inputHandler.parseP('e\u0301');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), 'e\u0301');
|
||||||
|
assert.strictEqual(bufferService.buffer.x, 1);
|
||||||
|
});
|
||||||
|
it('should join combining characters split across parse calls', async () => {
|
||||||
|
await inputHandler.parseP('e');
|
||||||
|
await inputHandler.parseP('\u0301');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), 'e\u0301');
|
||||||
|
assert.strictEqual(bufferService.buffer.x, 1);
|
||||||
|
});
|
||||||
|
it('should repeat preceding grapheme cluster via REP', async () => {
|
||||||
|
await inputHandler.parseP('e\u0301\x1b[2b');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), 'e\u0301e\u0301e\u0301');
|
||||||
|
assert.strictEqual(bufferService.buffer.x, 3);
|
||||||
|
});
|
||||||
|
it('should not repeat when REP has no preceding join state', async () => {
|
||||||
|
await inputHandler.parseP('\x1b[2b');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), '');
|
||||||
|
assert.strictEqual(bufferService.buffer.x, 0);
|
||||||
|
});
|
||||||
|
it('should not repeat after an intervening escape sequence', async () => {
|
||||||
|
await inputHandler.parseP('a\x1b[0m\x1b[2b');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), 'a');
|
||||||
|
assert.strictEqual(bufferService.buffer.x, 1);
|
||||||
});
|
});
|
||||||
it('should clear cells to the right on early wrap-around', async () => {
|
it('should clear cells to the right on early wrap-around', async () => {
|
||||||
bufferService.resize(5, 5);
|
bufferService.resize(5, 5);
|
||||||
@@ -667,6 +689,49 @@ describe('InputHandler', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ISO-2022 character sets', () => {
|
||||||
|
let charsetService: CharsetService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
charsetService = new CharsetService();
|
||||||
|
inputHandler = new TestInputHandler(
|
||||||
|
bufferService,
|
||||||
|
charsetService,
|
||||||
|
coreService,
|
||||||
|
new MockLogService(),
|
||||||
|
optionsService,
|
||||||
|
oscLinkService,
|
||||||
|
new MockMouseStateService(),
|
||||||
|
new MockUnicodeService()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map G0 line drawing via ESC ( 0', async () => {
|
||||||
|
await inputHandler.parseP('\x1b(0q\x1b(Bq');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), '\u2500q');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map G1 line drawing after ESC ) 0 and SO', async () => {
|
||||||
|
await inputHandler.parseP('\x1b)0\x0eq\x0f\x1b(Bq');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), '\u2500q');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should restore charset and glevel on ESC 7 / ESC 8', async () => {
|
||||||
|
await inputHandler.parseP('\x1b)0\x0e');
|
||||||
|
assert.strictEqual(charsetService.glevel, 1);
|
||||||
|
assert.strictEqual(charsetService.charset, CHARSETS['0']);
|
||||||
|
await inputHandler.parseP('\x1b7');
|
||||||
|
await inputHandler.parseP('\x0f\x1b(B');
|
||||||
|
assert.strictEqual(charsetService.glevel, 0);
|
||||||
|
assert.ok(charsetService.charset === undefined);
|
||||||
|
await inputHandler.parseP('\x1b8');
|
||||||
|
assert.strictEqual(charsetService.glevel, 1);
|
||||||
|
assert.strictEqual(charsetService.charset, CHARSETS['0']);
|
||||||
|
await inputHandler.parseP('q');
|
||||||
|
assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), '\u2500');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('alt screen', () => {
|
describe('alt screen', () => {
|
||||||
let bufferService: IBufferService;
|
let bufferService: IBufferService;
|
||||||
let handler: TestInputHandler;
|
let handler: TestInputHandler;
|
||||||
@@ -1181,6 +1246,19 @@ describe('InputHandler', () => {
|
|||||||
await inputHandler.parseP('\x1b[100;100H');
|
await inputHandler.parseP('\x1b[100;100H');
|
||||||
assert.deepEqual(getCursor(bufferService), [9, 9]);
|
assert.deepEqual(getCursor(bufferService), [9, 9]);
|
||||||
});
|
});
|
||||||
|
it('cursor position (CUP) with DECOM and scroll margins', async () => {
|
||||||
|
await inputHandler.parseP('\x1b[?6h\x1b[2;3r\x1b[1;1H');
|
||||||
|
assert.deepEqual(getCursor(bufferService), [0, 1]);
|
||||||
|
await inputHandler.parseP('X');
|
||||||
|
assert.strictEqual(getLines(bufferService, 3)[1], 'X');
|
||||||
|
await inputHandler.parseP('\x1b[2;1H');
|
||||||
|
assert.deepEqual(getCursor(bufferService), [0, 2]);
|
||||||
|
await inputHandler.parseP('\x1b[10;10H');
|
||||||
|
assert.deepEqual(getCursor(bufferService), [9, 2]);
|
||||||
|
await inputHandler.parseP('\x1b[?6l');
|
||||||
|
await inputHandler.parseP('\x1b[2;1H');
|
||||||
|
assert.deepEqual(getCursor(bufferService), [0, 1]);
|
||||||
|
});
|
||||||
it('horizontal position absolute (HPA)', async () => {
|
it('horizontal position absolute (HPA)', async () => {
|
||||||
await inputHandler.parseP('\x1b[`');
|
await inputHandler.parseP('\x1b[`');
|
||||||
assert.deepEqual(getCursor(bufferService), [0, 0]);
|
assert.deepEqual(getCursor(bufferService), [0, 0]);
|
||||||
@@ -1240,7 +1318,7 @@ describe('InputHandler', () => {
|
|||||||
await inputHandler.parseP('\x1b[e');
|
await inputHandler.parseP('\x1b[e');
|
||||||
assert.deepEqual(getCursor(bufferService), [8, 5]);
|
assert.deepEqual(getCursor(bufferService), [8, 5]);
|
||||||
});
|
});
|
||||||
describe('should clamp cursor into addressible range', () => {
|
describe('should clamp cursor into addressable range', () => {
|
||||||
it('CUF', async () => {
|
it('CUF', async () => {
|
||||||
bufferService.buffer.x = 10000;
|
bufferService.buffer.x = 10000;
|
||||||
bufferService.buffer.y = 10000;
|
bufferService.buffer.y = 10000;
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType, SpecialColorIndex } from './Types';
|
import { IInputHandler, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType, SpecialColorIndex } from './Types';
|
||||||
|
import { IAttributeData, IBuffer } from './buffer/Types';
|
||||||
import { C0, C1 } from './data/EscapeSequences';
|
import { C0, C1 } from './data/EscapeSequences';
|
||||||
import { CHARSETS, DEFAULT_CHARSET } from './data/Charsets';
|
import { CHARSETS, DEFAULT_CHARSET } from './data/Charsets';
|
||||||
import { EscapeSequenceParser } from './parser/EscapeSequenceParser';
|
import { EscapeSequenceParser } from './parser/EscapeSequenceParser';
|
||||||
@@ -20,7 +21,6 @@ import { UnicodeService } from './services/UnicodeService';
|
|||||||
import { OscHandler } from './parser/OscParser';
|
import { OscHandler } from './parser/OscParser';
|
||||||
import { DcsHandler } from './parser/DcsParser';
|
import { DcsHandler } from './parser/DcsParser';
|
||||||
import { ApcHandler } from './parser/ApcParser';
|
import { ApcHandler } from './parser/ApcParser';
|
||||||
import { IBuffer } from './buffer/Types';
|
|
||||||
import { parseColor } from './input/XParseColor';
|
import { parseColor } from './input/XParseColor';
|
||||||
import { Emitter } from './Event';
|
import { Emitter } from './Event';
|
||||||
import { XTERM_VERSION } from './Version';
|
import { XTERM_VERSION } from './Version';
|
||||||
@@ -3065,7 +3065,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
const idx = slots.shift() as string;
|
const idx = slots.shift() as string;
|
||||||
const spec = slots.shift() as string;
|
const spec = slots.shift() as string;
|
||||||
if (/^\d+$/.exec(idx)) {
|
if (/^\d+$/.exec(idx)) {
|
||||||
const index = parseInt(idx);
|
const index = parseInt(idx, 10);
|
||||||
if (isValidColorIndex(index)) {
|
if (isValidColorIndex(index)) {
|
||||||
if (spec === '?') {
|
if (spec === '?') {
|
||||||
event.push({ type: ColorRequestType.REPORT, index });
|
event.push({ type: ColorRequestType.REPORT, index });
|
||||||
@@ -3228,7 +3228,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
const slots = data.split(';');
|
const slots = data.split(';');
|
||||||
for (let i = 0; i < slots.length; ++i) {
|
for (let i = 0; i < slots.length; ++i) {
|
||||||
if (/^\d+$/.exec(slots[i])) {
|
if (/^\d+$/.exec(slots[i])) {
|
||||||
const index = parseInt(slots[i]);
|
const index = parseInt(slots[i], 10);
|
||||||
if (isValidColorIndex(index)) {
|
if (isValidColorIndex(index)) {
|
||||||
event.push({ type: ColorRequestType.RESTORE, index });
|
event.push({ type: ColorRequestType.RESTORE, index });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ export function getSafariVersion(): number {
|
|||||||
if (majorVersion === null || majorVersion.length < 2) {
|
if (majorVersion === null || majorVersion.length < 2) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return parseInt(majorVersion[1]);
|
return parseInt(majorVersion[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the users platform. We use this to interpret the meta key
|
// Find the user's platform. We use this to interpret the meta key
|
||||||
// and ISO third level shifts.
|
// and ISO third level shifts.
|
||||||
// http://stackoverflow.com/q/19877924/577598
|
// http://stackoverflow.com/q/19877924/577598
|
||||||
export const isMac = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'].includes(platform);
|
export const isMac = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'].includes(platform);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { ILogService } from './services/Services';
|
|||||||
interface ITaskQueue {
|
interface ITaskQueue {
|
||||||
/**
|
/**
|
||||||
* Adds a task to the queue which will run in a future idle callback.
|
* Adds a task to the queue which will run in a future idle callback.
|
||||||
* To avoid perceivable stalls on the mainthread, tasks with heavy workload
|
* To avoid perceivable stalls on the main thread, tasks with heavy workload
|
||||||
* should split their work into smaller pieces and return `true` to get
|
* should split their work into smaller pieces and return `true` to get
|
||||||
* called again until the work is done (on falsy return value).
|
* called again until the work is done (on falsy return value).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IMouseStateService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService, type IBufferResizeEvent } from './services/Services';
|
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IMouseStateService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService, type IBufferResizeEvent } from './services/Services';
|
||||||
import { UnicodeService } from './services/UnicodeService';
|
import { UnicodeService } from './services/UnicodeService';
|
||||||
import { DEFAULT_OPTIONS } from './services/OptionsService';
|
import { DEFAULT_OPTIONS } from './services/OptionsService';
|
||||||
import { IBufferSet, IBuffer } from './buffer/Types';
|
import { IAttributeData, IBuffer, IBufferLine, IBufferSet, IExtendedAttrs } from './buffer/Types';
|
||||||
import { BufferSet } from './buffer/BufferSet';
|
import { BufferSet } from './buffer/BufferSet';
|
||||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable, IBufferLine, IExtendedAttrs } from './Types';
|
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IOscLinkData, IDisposable } from './Types';
|
||||||
import { UnicodeV6 } from './input/UnicodeV6';
|
import { UnicodeV6 } from './input/UnicodeV6';
|
||||||
import { IDecorationOptions, IDecoration } from '@xterm/xterm';
|
import { IDecorationOptions, IDecoration } from '@xterm/xterm';
|
||||||
import { Emitter, type IEvent } from './Event';
|
import { Emitter, type IEvent } from './Event';
|
||||||
|
|||||||
+25
-183
@@ -3,26 +3,32 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDeleteEvent, IInsertEvent } from './CircularList';
|
|
||||||
import { UnderlineStyle } from './buffer/Constants';
|
|
||||||
import { IBufferSet } from './buffer/Types';
|
|
||||||
import { IParams } from './parser/Types';
|
|
||||||
import { IMouseStateService, ICoreService, IOptionsService, IUnicodeService } from './services/Services';
|
|
||||||
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm';
|
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm';
|
||||||
import type { Emitter, IEvent } from './Event';
|
import type { IEvent } from './Event';
|
||||||
|
|
||||||
export interface ICoreTerminal {
|
/** sequence params serialized to js arrays */
|
||||||
mouseStateService: IMouseStateService;
|
export type ParamsArray = (number | number[])[];
|
||||||
coreService: ICoreService;
|
|
||||||
optionsService: IOptionsService;
|
/** Interface of Params storage class. */
|
||||||
unicodeService: IUnicodeService;
|
export interface IParams {
|
||||||
buffers: IBufferSet;
|
/** from ctor */
|
||||||
options: Required<ITerminalOptions>;
|
maxLength: number;
|
||||||
registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
|
maxSubParamsLength: number;
|
||||||
registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
|
|
||||||
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
|
/** param values and its length */
|
||||||
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
params: Int32Array;
|
||||||
registerApcHandler(id: IFunctionIdentifier, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
length: number;
|
||||||
|
|
||||||
|
/** methods */
|
||||||
|
clone(): IParams;
|
||||||
|
toArray(): ParamsArray;
|
||||||
|
reset(): void;
|
||||||
|
resetZdm(): void;
|
||||||
|
addParam(value: number): void;
|
||||||
|
addSubParam(value: number): void;
|
||||||
|
hasSubParams(idx: number): boolean;
|
||||||
|
getSubParams(idx: number): Int32Array | null;
|
||||||
|
getSubParamsAll(): {[idx: number]: Int32Array};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDisposable {
|
export interface IDisposable {
|
||||||
@@ -62,28 +68,6 @@ export interface IScrollEvent {
|
|||||||
position: number;
|
position: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICircularList<T> {
|
|
||||||
length: number;
|
|
||||||
maxLength: number;
|
|
||||||
isFull: boolean;
|
|
||||||
|
|
||||||
onDeleteEmitter: Emitter<IDeleteEvent>;
|
|
||||||
onDelete: IEvent<IDeleteEvent>;
|
|
||||||
onInsertEmitter: Emitter<IInsertEvent>;
|
|
||||||
onInsert: IEvent<IInsertEvent>;
|
|
||||||
onTrimEmitter: Emitter<number>;
|
|
||||||
onTrim: IEvent<number>;
|
|
||||||
|
|
||||||
get(index: number): T | undefined;
|
|
||||||
set(index: number, value: T): void;
|
|
||||||
push(value: T): void;
|
|
||||||
recycle(): T;
|
|
||||||
pop(): T | undefined;
|
|
||||||
splice(start: number, deleteCount: number, ...items: T[]): void;
|
|
||||||
trimStart(count: number): void;
|
|
||||||
shiftElements(start: number, count: number, offset: number): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum KeyboardResultType {
|
export const enum KeyboardResultType {
|
||||||
SEND_KEY,
|
SEND_KEY,
|
||||||
SELECT_ALL,
|
SELECT_ALL,
|
||||||
@@ -101,24 +85,12 @@ export interface ICharset {
|
|||||||
[key: string]: string | undefined;
|
[key: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CharData = [attr: number, char: string, width: number, code: number];
|
|
||||||
|
|
||||||
export interface IColor {
|
export interface IColor {
|
||||||
readonly css: string;
|
readonly css: string;
|
||||||
readonly rgba: number; // 32-bit int with rgba in each byte
|
readonly rgba: number; // 32-bit int with rgba in each byte
|
||||||
}
|
}
|
||||||
export type IColorRGB = [red: number, green: number, blue: number];
|
export type IColorRGB = [red: number, green: number, blue: number];
|
||||||
|
|
||||||
export interface IExtendedAttrs {
|
|
||||||
ext: number;
|
|
||||||
underlineStyle: UnderlineStyle;
|
|
||||||
underlineColor: number;
|
|
||||||
underlineVariantOffset: number;
|
|
||||||
urlId: number;
|
|
||||||
clone(): IExtendedAttrs;
|
|
||||||
isEmpty(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the current hyperlink. Since these are treated as extended attirbutes, these get passed on
|
* Tracks the current hyperlink. Since these are treated as extended attirbutes, these get passed on
|
||||||
* to the linkifier when anything is printed. Doing it this way ensures that even when the cursor
|
* to the linkifier when anything is printed. Doing it this way ensures that even when the cursor
|
||||||
@@ -130,136 +102,6 @@ export interface IOscLinkData {
|
|||||||
uri: string;
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An object that represents all attributes of a cell.
|
|
||||||
*/
|
|
||||||
export interface IAttributeData {
|
|
||||||
/**
|
|
||||||
* "fg" is a 32-bit unsigned integer that stores the foreground color of the cell in the 24 least
|
|
||||||
* significant bits and additional flags in the remaining 8 bits.
|
|
||||||
*/
|
|
||||||
fg: number;
|
|
||||||
/**
|
|
||||||
* "bg" is a 32-bit unsigned integer that stores the background color of the cell in the 24 least
|
|
||||||
* significant bits and additional flags in the remaining 8 bits.
|
|
||||||
*/
|
|
||||||
bg: number;
|
|
||||||
/**
|
|
||||||
* "extended", aka "ext", stores extended attributes beyond those available in fg and bg. This
|
|
||||||
* data is optional on a cell and encodes less common data.
|
|
||||||
*/
|
|
||||||
extended: IExtendedAttrs;
|
|
||||||
|
|
||||||
clone(): IAttributeData;
|
|
||||||
|
|
||||||
// flags
|
|
||||||
isInverse(): number;
|
|
||||||
isBold(): number;
|
|
||||||
isUnderline(): number;
|
|
||||||
isBlink(): number;
|
|
||||||
isInvisible(): number;
|
|
||||||
isItalic(): number;
|
|
||||||
isDim(): number;
|
|
||||||
isStrikethrough(): number;
|
|
||||||
isProtected(): number;
|
|
||||||
isOverline(): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The color mode of the foreground color which determines how to decode {@link getFgColor},
|
|
||||||
* possible values include {@link Attributes.CM_DEFAULT}, {@link Attributes.CM_P16},
|
|
||||||
* {@link Attributes.CM_P256} and {@link Attributes.CM_RGB}.
|
|
||||||
*/
|
|
||||||
getFgColorMode(): number;
|
|
||||||
/**
|
|
||||||
* The color mode of the background color which determines how to decode {@link getBgColor},
|
|
||||||
* possible values include {@link Attributes.CM_DEFAULT}, {@link Attributes.CM_P16},
|
|
||||||
* {@link Attributes.CM_P256} and {@link Attributes.CM_RGB}.
|
|
||||||
*/
|
|
||||||
getBgColorMode(): number;
|
|
||||||
isFgRGB(): boolean;
|
|
||||||
isBgRGB(): boolean;
|
|
||||||
isFgPalette(): boolean;
|
|
||||||
isBgPalette(): boolean;
|
|
||||||
isFgDefault(): boolean;
|
|
||||||
isBgDefault(): boolean;
|
|
||||||
isAttributeDefault(): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an integer representation of the foreground color, how to decode the color depends on the
|
|
||||||
* color mode {@link getFgColorMode}.
|
|
||||||
*/
|
|
||||||
getFgColor(): number;
|
|
||||||
/**
|
|
||||||
* Gets an integer representation of the background color, how to decode the color depends on the
|
|
||||||
* color mode {@link getBgColorMode}.
|
|
||||||
*/
|
|
||||||
getBgColor(): number;
|
|
||||||
|
|
||||||
// extended attrs
|
|
||||||
hasExtendedAttrs(): number;
|
|
||||||
updateExtended(): void;
|
|
||||||
getUnderlineColor(): number;
|
|
||||||
getUnderlineColorMode(): number;
|
|
||||||
isUnderlineColorRGB(): boolean;
|
|
||||||
isUnderlineColorPalette(): boolean;
|
|
||||||
isUnderlineColorDefault(): boolean;
|
|
||||||
getUnderlineStyle(): number;
|
|
||||||
getUnderlineVariantOffset(): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Cell data */
|
|
||||||
export interface ICellData extends IAttributeData {
|
|
||||||
content: number;
|
|
||||||
combinedData: string;
|
|
||||||
isCombined(): number;
|
|
||||||
getWidth(): number;
|
|
||||||
getChars(): string;
|
|
||||||
getCode(): number;
|
|
||||||
setFromCharData(value: CharData): void;
|
|
||||||
getAsCharData(): CharData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a line in the terminal buffer.
|
|
||||||
*/
|
|
||||||
export interface IBufferLine {
|
|
||||||
length: number;
|
|
||||||
isWrapped: boolean;
|
|
||||||
get(index: number): CharData;
|
|
||||||
set(index: number, value: CharData): void;
|
|
||||||
loadCell(index: number, cell: ICellData): ICellData;
|
|
||||||
setCell(index: number, cell: ICellData): void;
|
|
||||||
setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void;
|
|
||||||
addCodepointToCell(index: number, codePoint: number, width: number): void;
|
|
||||||
insertCells(pos: number, n: number, ch: ICellData): void;
|
|
||||||
deleteCells(pos: number, n: number, fill: ICellData): void;
|
|
||||||
replaceCells(start: number, end: number, fill: ICellData, respectProtect?: boolean): void;
|
|
||||||
resize(cols: number, fill: ICellData): boolean;
|
|
||||||
cleanupMemory(): number;
|
|
||||||
fill(fillCellData: ICellData, respectProtect?: boolean): void;
|
|
||||||
copyFrom(line: IBufferLine): void;
|
|
||||||
clone(): IBufferLine;
|
|
||||||
getTrimmedLength(): number;
|
|
||||||
getNoBgTrimmedLength(): number;
|
|
||||||
translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;
|
|
||||||
|
|
||||||
/* direct access to cell attrs */
|
|
||||||
getWidth(index: number): number;
|
|
||||||
hasWidth(index: number): number;
|
|
||||||
getFg(index: number): number;
|
|
||||||
getBg(index: number): number;
|
|
||||||
hasContent(index: number): number;
|
|
||||||
getCodePoint(index: number): number;
|
|
||||||
isCombined(index: number): number;
|
|
||||||
getString(index: number): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMarker extends IDisposable {
|
|
||||||
readonly id: number;
|
|
||||||
readonly isDisposed: boolean;
|
|
||||||
readonly line: number;
|
|
||||||
onDispose: IEvent<void>;
|
|
||||||
}
|
|
||||||
export interface IModes {
|
export interface IModes {
|
||||||
insertMode: boolean;
|
insertMode: boolean;
|
||||||
}
|
}
|
||||||
@@ -339,7 +181,7 @@ export interface ICoreMouseEvent {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
/**
|
/**
|
||||||
* Button the action occured. Due to restrictions of the tracking protocols
|
* Button the action occurred. Due to restrictions of the tracking protocols
|
||||||
* it is not possible to report multiple buttons at once.
|
* it is not possible to report multiple buttons at once.
|
||||||
* Wheel is treated as a button.
|
* Wheel is treated as a button.
|
||||||
* There are invalid combinations of buttons and actions possible
|
* There are invalid combinations of buttons and actions possible
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { DEFAULT_ATTR_DATA } from './buffer/BufferLine';
|
||||||
|
import { updateWindowsModeWrappedState } from './WindowsMode';
|
||||||
|
import { BufferService } from './services/BufferService';
|
||||||
|
import { OptionsService } from './services/OptionsService';
|
||||||
|
import { MockLogService } from './TestUtils.test';
|
||||||
|
|
||||||
|
describe('WindowsMode', () => {
|
||||||
|
describe('updateWindowsModeWrappedState', () => {
|
||||||
|
it('should mark the next line wrapped when the previous line ends in a non-whitespace character', () => {
|
||||||
|
const bufferService = new BufferService(new OptionsService({ rows: 5, cols: 10 }), new MockLogService());
|
||||||
|
const buffer = bufferService.buffer;
|
||||||
|
const previousLine = buffer.lines.get(buffer.ybase)!;
|
||||||
|
for (let i = 0; i < bufferService.cols; i++) {
|
||||||
|
previousLine!.setCellFromCodepoint(i, 'a'.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
||||||
|
}
|
||||||
|
buffer.y = 1;
|
||||||
|
|
||||||
|
updateWindowsModeWrappedState(bufferService);
|
||||||
|
|
||||||
|
assert.strictEqual(buffer.lines.get(buffer.ybase + 1)!.isWrapped, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not mark the next line wrapped when the previous line ends in whitespace', () => {
|
||||||
|
const bufferService = new BufferService(new OptionsService({ rows: 5, cols: 10 }), new MockLogService());
|
||||||
|
const buffer = bufferService.buffer;
|
||||||
|
const previousLine = buffer.lines.get(buffer.ybase)!;
|
||||||
|
for (let i = 0; i < bufferService.cols - 1; i++) {
|
||||||
|
previousLine!.setCellFromCodepoint(i, 'a'.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
||||||
|
}
|
||||||
|
previousLine!.setCellFromCodepoint(bufferService.cols - 1, ' '.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
||||||
|
buffer.y = 1;
|
||||||
|
|
||||||
|
updateWindowsModeWrappedState(bufferService);
|
||||||
|
|
||||||
|
assert.strictEqual(buffer.lines.get(buffer.ybase + 1)!.isWrapped, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not mark the next line wrapped when the previous line ends in a null cell', () => {
|
||||||
|
const bufferService = new BufferService(new OptionsService({ rows: 5, cols: 10 }), new MockLogService());
|
||||||
|
const buffer = bufferService.buffer;
|
||||||
|
buffer.y = 1;
|
||||||
|
|
||||||
|
updateWindowsModeWrappedState(bufferService);
|
||||||
|
|
||||||
|
assert.strictEqual(buffer.lines.get(buffer.ybase + 1)!.isWrapped, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IAttributeData, IColorRGB, IExtendedAttrs } from '../Types';
|
import { IColorRGB } from '../Types';
|
||||||
|
import { IAttributeData, IExtendedAttrs } from './Types';
|
||||||
import { Attributes, FgFlags, BgFlags, UnderlineStyle, ExtFlags } from './Constants';
|
import { Attributes, FgFlags, BgFlags, UnderlineStyle, ExtFlags } from './Constants';
|
||||||
|
|
||||||
export class AttributeData implements IAttributeData {
|
export class AttributeData implements IAttributeData {
|
||||||
|
|||||||
@@ -233,6 +233,27 @@ describe('Buffer', () => {
|
|||||||
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
assert.equal(buffer.lines.length, INIT_ROWS + 10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Windows ConPTY', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
optionsService.options.windowsPty = { backend: 'conpty', buildNumber: 19000 };
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not adjust ybase or ydisp when growing rows', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA));
|
||||||
|
}
|
||||||
|
buffer.y = INIT_ROWS - 1;
|
||||||
|
buffer.ybase = 10;
|
||||||
|
buffer.ydisp = 10;
|
||||||
|
const linesBefore = buffer.lines.length;
|
||||||
|
buffer.resize(INIT_COLS, INIT_ROWS + 5);
|
||||||
|
assert.equal(buffer.ybase, 10);
|
||||||
|
assert.equal(buffer.ydisp, 10);
|
||||||
|
assert.equal(buffer.lines.length, linesBefore + 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('row and column increased', () => {
|
describe('row and column increased', () => {
|
||||||
@@ -305,6 +326,71 @@ describe('Buffer', () => {
|
|||||||
assert.equal(buffer.lines.get(8)!.translateToString(), ' ');
|
assert.equal(buffer.lines.get(8)!.translateToString(), ' ');
|
||||||
assert.equal(buffer.lines.get(9)!.translateToString(), ' ');
|
assert.equal(buffer.lines.get(9)!.translateToString(), ' ');
|
||||||
});
|
});
|
||||||
|
it('should gate reflow on ConPTY buildNumber 21376', () => {
|
||||||
|
const prepareWrappedShrink = () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
const firstLine = buffer.lines.get(0)!;
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const code = 'a'.charCodeAt(0) + i;
|
||||||
|
firstLine.set(i, [0, String.fromCharCode(code), 1, code]);
|
||||||
|
}
|
||||||
|
buffer.y = 1;
|
||||||
|
buffer.resize(1, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
optionsService.options.windowsPty = { backend: 'conpty', buildNumber: 21375 };
|
||||||
|
prepareWrappedShrink();
|
||||||
|
assert.equal(buffer.lines.get(1)!.translateToString().trim(), '');
|
||||||
|
|
||||||
|
buffer = new TestBuffer(true, optionsService, bufferService, new MockLogService());
|
||||||
|
optionsService.options.windowsPty = { backend: 'conpty', buildNumber: 21376 };
|
||||||
|
prepareWrappedShrink();
|
||||||
|
assert.equal(buffer.lines.get(1)!.translateToString().trim(), 'b');
|
||||||
|
assert.ok(buffer.lines.get(1)!.isWrapped);
|
||||||
|
});
|
||||||
|
it('should unwrap lines on ConPTY builds with reflow support', () => {
|
||||||
|
optionsService.options.windowsPty = { backend: 'conpty', buildNumber: 21376 };
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
const firstLine = buffer.lines.get(0)!;
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const code = 'a'.charCodeAt(0) + i;
|
||||||
|
firstLine.set(i, [0, String.fromCharCode(code), 1, code]);
|
||||||
|
}
|
||||||
|
buffer.y = 1;
|
||||||
|
buffer.resize(1, 10);
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
assert.equal(buffer.lines.get(0)!.translateToString(), 'abcde');
|
||||||
|
assert.equal(buffer.lines.get(1)!.translateToString(), ' ');
|
||||||
|
});
|
||||||
|
it('should reflow wrapped lines containing the cursor when reflowCursorLine is enabled', () => {
|
||||||
|
optionsService.options.reflowCursorLine = true;
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
const firstLine = buffer.lines.get(0)!;
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const code = 'a'.charCodeAt(0) + i;
|
||||||
|
firstLine.set(i, [0, String.fromCharCode(code), 1, code]);
|
||||||
|
}
|
||||||
|
buffer.resize(1, 10);
|
||||||
|
buffer.y = 2;
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
assert.equal(buffer.lines.get(0)!.translateToString(), 'abcde');
|
||||||
|
});
|
||||||
|
it('should not reflow wrapped lines containing the cursor by default', () => {
|
||||||
|
buffer.fillViewportRows();
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
const firstLine = buffer.lines.get(0)!;
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const code = 'a'.charCodeAt(0) + i;
|
||||||
|
firstLine.set(i, [0, String.fromCharCode(code), 1, code]);
|
||||||
|
}
|
||||||
|
buffer.resize(1, 10);
|
||||||
|
buffer.y = 2;
|
||||||
|
buffer.resize(5, 10);
|
||||||
|
assert.notEqual(buffer.lines.get(0)!.translateToString(), 'abcde');
|
||||||
|
});
|
||||||
it('should discard parts of wrapped lines that go out of the scrollback', () => {
|
it('should discard parts of wrapped lines that go out of the scrollback', () => {
|
||||||
buffer.fillViewportRows();
|
buffer.fillViewportRows();
|
||||||
optionsService.options.scrollback = 1;
|
optionsService.options.scrollback = 1;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import { CircularList, IInsertEvent } from '../CircularList';
|
import { CircularList, IInsertEvent } from '../CircularList';
|
||||||
import { Disposable, toDisposable } from '../Lifecycle';
|
import { Disposable, toDisposable } from '../Lifecycle';
|
||||||
import { IdleTaskQueue } from '../TaskQueue';
|
import { IdleTaskQueue } from '../TaskQueue';
|
||||||
import { IAttributeData, IBufferLine, ICellData, ICharset } from '../Types';
|
import { ICharset } from '../Types';
|
||||||
|
import { IAttributeData, IBuffer, IBufferLine, ICellData } from './Types';
|
||||||
import { ExtendedAttrs } from './AttributeData';
|
import { ExtendedAttrs } from './AttributeData';
|
||||||
import { BufferLine, DEFAULT_ATTR_DATA } from './BufferLine';
|
import { BufferLine, DEFAULT_ATTR_DATA } from './BufferLine';
|
||||||
import { BufferLineStringCache } from './BufferLineStringCache';
|
import { BufferLineStringCache } from './BufferLineStringCache';
|
||||||
@@ -14,7 +15,6 @@ import { getWrappedLineTrimmedLength, reflowLargerApplyNewLayout, reflowLargerCr
|
|||||||
import { CellData } from './CellData';
|
import { CellData } from './CellData';
|
||||||
import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, WHITESPACE_CELL_WIDTH } from './Constants';
|
import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, WHITESPACE_CELL_WIDTH } from './Constants';
|
||||||
import { Marker } from './Marker';
|
import { Marker } from './Marker';
|
||||||
import { IBuffer } from './Types';
|
|
||||||
import { DEFAULT_CHARSET } from '../data/Charsets';
|
import { DEFAULT_CHARSET } from '../data/Charsets';
|
||||||
import { IBufferService, ILogService, IOptionsService } from '../services/Services';
|
import { IBufferService, ILogService, IOptionsService } from '../services/Services';
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ export class Buffer extends Disposable implements IBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the buffer to it's initial state, discarding all previous data.
|
* Clears the buffer to its initial state, discarding all previous data.
|
||||||
*/
|
*/
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
this._stringCache.clear();
|
this._stringCache.clear();
|
||||||
@@ -197,7 +197,7 @@ export class Buffer extends Disposable implements IBuffer {
|
|||||||
for (let y = this._rows; y < newRows; y++) {
|
for (let y = this._rows; y < newRows; y++) {
|
||||||
if (this.lines.length < newRows + this.ybase) {
|
if (this.lines.length < newRows + this.ybase) {
|
||||||
if (this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined) {
|
if (this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined) {
|
||||||
// Just add the new missing rows on Windows as conpty reprints the screen with it's
|
// Just add the new missing rows on Windows as conpty reprints the screen with its
|
||||||
// view of the world. Once a line enters scrollback for conpty it remains there
|
// view of the world. Once a line enters scrollback for conpty it remains there
|
||||||
this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
|
this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content,
|
|||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
import { BufferLineStringCache } from './BufferLineStringCache';
|
import { BufferLineStringCache } from './BufferLineStringCache';
|
||||||
import { CellData } from './CellData';
|
import { CellData } from './CellData';
|
||||||
import { CharData, IBufferLine, ICellData } from '../Types';
|
import { CharData, IBufferLine, ICellData } from './Types';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { AttributeData } from './AttributeData';
|
import { AttributeData } from './AttributeData';
|
||||||
import { createCellData, NULL_CELL_DATA, extendedAttributes } from '../TestUtils.test';
|
import { createCellData, NULL_CELL_DATA, extendedAttributes } from '../TestUtils.test';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CharData, IAttributeData, IBufferLine, ICellData, IExtendedAttrs } from '../Types';
|
import { CharData, IAttributeData, IBufferLine, ICellData, IExtendedAttrs } from './Types';
|
||||||
import { AttributeData } from './AttributeData';
|
import { AttributeData } from './AttributeData';
|
||||||
import { CellData } from './CellData';
|
import { CellData } from './CellData';
|
||||||
import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from './Constants';
|
import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from './Constants';
|
||||||
@@ -71,10 +71,20 @@ export interface IBufferLineStringCache {
|
|||||||
* (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop
|
* (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop
|
||||||
* memory allocs / GC pressure can be greatly reduced by reusing the CellData object.
|
* memory allocs / GC pressure can be greatly reduced by reusing the CellData object.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Shared empty sparse map for lines with no combining data.
|
||||||
|
* Uses a sentinel + copy-on-write so `copyFrom` can detect empty `_combined` with
|
||||||
|
* reference equality. `_extendedAttrs` uses `undefined` instead (addon-image writes
|
||||||
|
* the map directly without BufferLine APIs).
|
||||||
|
*/
|
||||||
|
const EMPTY_SPARSE_MAP: {[index: number]: string} = Object.create(null);
|
||||||
|
|
||||||
export class BufferLine implements IBufferLine {
|
export class BufferLine implements IBufferLine {
|
||||||
protected _data: Uint32Array;
|
protected _data: Uint32Array;
|
||||||
protected _combined: {[index: number]: string} = {};
|
/** Sparse cache; only read when `IS_COMBINED_MASK` is set in `_data`. */
|
||||||
protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
|
protected _combined: {[index: number]: string} = EMPTY_SPARSE_MAP;
|
||||||
|
/** Sparse cache; only read when `HAS_EXTENDED` is set in `_data`. */
|
||||||
|
protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} | undefined;
|
||||||
protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
|
protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
|
||||||
public length: number;
|
public length: number;
|
||||||
|
|
||||||
@@ -119,6 +129,7 @@ export class BufferLine implements IBufferLine {
|
|||||||
this._invalidateStringCache();
|
this._invalidateStringCache();
|
||||||
this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
|
this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
|
||||||
if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
|
if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
|
||||||
|
this._ensureCombinedMap();
|
||||||
this._combined[index] = value[1];
|
this._combined[index] = value[1];
|
||||||
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||||
} else {
|
} else {
|
||||||
@@ -205,9 +216,15 @@ export class BufferLine implements IBufferLine {
|
|||||||
cell.bg = this._data[$startIndex + Cell.BG];
|
cell.bg = this._data[$startIndex + Cell.BG];
|
||||||
if (cell.content & Content.IS_COMBINED_MASK) {
|
if (cell.content & Content.IS_COMBINED_MASK) {
|
||||||
cell.combinedData = this._combined[index];
|
cell.combinedData = this._combined[index];
|
||||||
|
} else {
|
||||||
|
cell.combinedData = '';
|
||||||
}
|
}
|
||||||
if (cell.bg & BgFlags.HAS_EXTENDED) {
|
if (cell.bg & BgFlags.HAS_EXTENDED) {
|
||||||
cell.extended = this._extendedAttrs[index]!;
|
cell.extended = this._extendedAttrs![index]!;
|
||||||
|
} else {
|
||||||
|
// Do not mutate cell.extended in place: it may still reference this line's map entry from a
|
||||||
|
// prior loadCell into a reused CellData (e.g. $workCell during insert/delete).
|
||||||
|
cell.extended = DEFAULT_ATTR_DATA.extended.clone();
|
||||||
}
|
}
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
@@ -218,10 +235,11 @@ export class BufferLine implements IBufferLine {
|
|||||||
public setCell(index: number, cell: ICellData): void {
|
public setCell(index: number, cell: ICellData): void {
|
||||||
this._invalidateStringCache();
|
this._invalidateStringCache();
|
||||||
if (cell.content & Content.IS_COMBINED_MASK) {
|
if (cell.content & Content.IS_COMBINED_MASK) {
|
||||||
|
this._ensureCombinedMap();
|
||||||
this._combined[index] = cell.combinedData;
|
this._combined[index] = cell.combinedData;
|
||||||
}
|
}
|
||||||
if (cell.bg & BgFlags.HAS_EXTENDED) {
|
if (cell.bg & BgFlags.HAS_EXTENDED) {
|
||||||
this._extendedAttrs[index] = cell.extended;
|
this._ensureExtendedAttrsMap()[index] = cell.extended;
|
||||||
}
|
}
|
||||||
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content;
|
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content;
|
||||||
this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg;
|
this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg;
|
||||||
@@ -236,7 +254,7 @@ export class BufferLine implements IBufferLine {
|
|||||||
public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
|
public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
|
||||||
this._invalidateStringCache();
|
this._invalidateStringCache();
|
||||||
if (attrs.bg & BgFlags.HAS_EXTENDED) {
|
if (attrs.bg & BgFlags.HAS_EXTENDED) {
|
||||||
this._extendedAttrs[index] = attrs.extended;
|
this._ensureExtendedAttrsMap()[index] = attrs.extended;
|
||||||
}
|
}
|
||||||
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
|
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
|
||||||
this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
|
this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
|
||||||
@@ -254,12 +272,14 @@ export class BufferLine implements IBufferLine {
|
|||||||
let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
|
let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
|
||||||
if (content & Content.IS_COMBINED_MASK) {
|
if (content & Content.IS_COMBINED_MASK) {
|
||||||
// we already have a combined string, simply add
|
// we already have a combined string, simply add
|
||||||
|
this._ensureCombinedMap();
|
||||||
this._combined[index] += stringFromCodePoint(codePoint);
|
this._combined[index] += stringFromCodePoint(codePoint);
|
||||||
} else {
|
} else {
|
||||||
if (content & Content.CODEPOINT_MASK) {
|
if (content & Content.CODEPOINT_MASK) {
|
||||||
// normal case for combining chars:
|
// normal case for combining chars:
|
||||||
// - move current leading char + new one into combined string
|
// - move current leading char + new one into combined string
|
||||||
// - set combined flag
|
// - set combined flag
|
||||||
|
this._ensureCombinedMap();
|
||||||
this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);
|
this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);
|
||||||
content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0
|
content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0
|
||||||
content |= Content.IS_COMBINED_MASK;
|
content |= Content.IS_COMBINED_MASK;
|
||||||
@@ -402,11 +422,13 @@ export class BufferLine implements IBufferLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove any cut off extended attributes
|
// remove any cut off extended attributes
|
||||||
const extKeys = Object.keys(this._extendedAttrs);
|
if (this._extendedAttrs) {
|
||||||
for (let i = 0; i < extKeys.length; i++) {
|
const extKeys = Object.keys(this._extendedAttrs);
|
||||||
const key = parseInt(extKeys[i], 10);
|
for (let i = 0; i < extKeys.length; i++) {
|
||||||
if (key >= cols) {
|
const key = parseInt(extKeys[i], 10);
|
||||||
delete this._extendedAttrs[key];
|
if (key >= cols) {
|
||||||
|
delete this._extendedAttrs[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,8 +464,8 @@ export class BufferLine implements IBufferLine {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._combined = {};
|
this._combined = EMPTY_SPARSE_MAP;
|
||||||
this._extendedAttrs = {};
|
this._extendedAttrs = undefined;
|
||||||
for (let i = 0; i < this.length; ++i) {
|
for (let i = 0; i < this.length; ++i) {
|
||||||
this.setCell(i, fillCellData);
|
this.setCell(i, fillCellData);
|
||||||
}
|
}
|
||||||
@@ -459,14 +481,7 @@ export class BufferLine implements IBufferLine {
|
|||||||
this._data.set(line._data);
|
this._data.set(line._data);
|
||||||
}
|
}
|
||||||
this.length = line.length;
|
this.length = line.length;
|
||||||
this._combined = {};
|
this._copySparseMapsFrom(line);
|
||||||
for (const el in line._combined) {
|
|
||||||
this._combined[el] = line._combined[el];
|
|
||||||
}
|
|
||||||
this._extendedAttrs = {};
|
|
||||||
for (const el in line._extendedAttrs) {
|
|
||||||
this._extendedAttrs[el] = line._extendedAttrs[el];
|
|
||||||
}
|
|
||||||
this.isWrapped = line.isWrapped;
|
this.isWrapped = line.isWrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,12 +490,7 @@ export class BufferLine implements IBufferLine {
|
|||||||
const newLine = new BufferLine(this._stringCache, 0, undefined, false);
|
const newLine = new BufferLine(this._stringCache, 0, undefined, false);
|
||||||
newLine._data = new Uint32Array(this._data);
|
newLine._data = new Uint32Array(this._data);
|
||||||
newLine.length = this.length;
|
newLine.length = this.length;
|
||||||
for (const el in this._combined) {
|
newLine._copySparseMapsFrom(this);
|
||||||
newLine._combined[el] = this._combined[el];
|
|
||||||
}
|
|
||||||
for (const el in this._extendedAttrs) {
|
|
||||||
newLine._extendedAttrs[el] = this._extendedAttrs[el];
|
|
||||||
}
|
|
||||||
newLine.isWrapped = this.isWrapped;
|
newLine.isWrapped = this.isWrapped;
|
||||||
return newLine;
|
return newLine;
|
||||||
}
|
}
|
||||||
@@ -511,27 +521,14 @@ export class BufferLine implements IBufferLine {
|
|||||||
for (let i = 0; i < Constants.CELL_INDICIES; i++) {
|
for (let i = 0; i < Constants.CELL_INDICIES; i++) {
|
||||||
this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
|
this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
|
||||||
}
|
}
|
||||||
if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
this._copyCellMapsFrom(src, srcCol + cell, destCol + cell);
|
||||||
this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let cell = 0; cell < length; cell++) {
|
for (let cell = 0; cell < length; cell++) {
|
||||||
for (let i = 0; i < Constants.CELL_INDICIES; i++) {
|
for (let i = 0; i < Constants.CELL_INDICIES; i++) {
|
||||||
this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
|
this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
|
||||||
}
|
}
|
||||||
if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
this._copyCellMapsFrom(src, srcCol + cell, destCol + cell);
|
||||||
this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move any combined data over as needed, FIXME: repeat for extended attrs
|
|
||||||
const srcCombinedKeys = Object.keys(src._combined);
|
|
||||||
for (let i = 0; i < srcCombinedKeys.length; i++) {
|
|
||||||
const key = parseInt(srcCombinedKeys[i], 10);
|
|
||||||
if (key >= srcCol) {
|
|
||||||
this._combined[key - srcCol + destCol] = src._combined[key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,4 +617,62 @@ export class BufferLine implements IBufferLine {
|
|||||||
cacheEntry.isTrimmed = false;
|
cacheEntry.isTrimmed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Copy sparse map entries for a single cell when `_data` flags require them. */
|
||||||
|
private _copyCellMapsFrom(src: BufferLine, srcCol: number, destCol: number): void {
|
||||||
|
const srcStart = srcCol * Constants.CELL_INDICIES;
|
||||||
|
if (src._data[srcStart + Cell.CONTENT] & Content.IS_COMBINED_MASK) {
|
||||||
|
this._ensureCombinedMap();
|
||||||
|
this._combined[destCol] = src._combined[srcCol];
|
||||||
|
}
|
||||||
|
if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||||
|
this._ensureExtendedAttrsMap()[destCol] = src._extendedAttrs![srcCol]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rebuild sparse maps from another line, keyed only by `_data` flags. */
|
||||||
|
private _copySparseMapsFrom(line: BufferLine): void {
|
||||||
|
const srcCombined = line._combined;
|
||||||
|
const srcExtended = line._extendedAttrs;
|
||||||
|
|
||||||
|
// Fast path: blank / scroll-recycle source (empty `_combined` sentinel, no extended map).
|
||||||
|
if (srcCombined === EMPTY_SPARSE_MAP && !srcExtended) {
|
||||||
|
this._combined = EMPTY_SPARSE_MAP;
|
||||||
|
this._extendedAttrs = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._copySparseMapsFromDense(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copy sparse maps by scanning `_data` flags (inlined, no per-cell function calls). */
|
||||||
|
private _copySparseMapsFromDense(line: BufferLine): void {
|
||||||
|
this._combined = {};
|
||||||
|
this._extendedAttrs = {};
|
||||||
|
const srcData = line._data;
|
||||||
|
const srcCombined = line._combined;
|
||||||
|
const srcExtended = line._extendedAttrs;
|
||||||
|
const len = line.length;
|
||||||
|
const indices = Constants.CELL_INDICIES;
|
||||||
|
const combined = this._combined;
|
||||||
|
const extendedAttrs = this._extendedAttrs;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const start = i * indices;
|
||||||
|
if (srcData[start + Cell.CONTENT] & Content.IS_COMBINED_MASK) {
|
||||||
|
combined[i] = srcCombined[i];
|
||||||
|
}
|
||||||
|
if (srcData[start + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||||
|
extendedAttrs[i] = srcExtended![i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ensureCombinedMap(): void {
|
||||||
|
if (this._combined === EMPTY_SPARSE_MAP) {
|
||||||
|
this._combined = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ensureExtendedAttrsMap(): {[index: number]: IExtendedAttrs | undefined} {
|
||||||
|
return this._extendedAttrs ??= {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
import { CircularList } from '../CircularList';
|
||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
import { BufferLineStringCache } from './BufferLineStringCache';
|
import { BufferLineStringCache } from './BufferLineStringCache';
|
||||||
|
import { CellData } from './CellData';
|
||||||
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Constants';
|
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Constants';
|
||||||
import { reflowSmallerGetNewLineLengths } from './BufferReflow';
|
import { reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow';
|
||||||
|
import { IBufferLine } from './Types';
|
||||||
|
|
||||||
const TEST_STRING_CACHE = new BufferLineStringCache();
|
const TEST_STRING_CACHE = new BufferLineStringCache();
|
||||||
|
|
||||||
@@ -93,4 +96,31 @@ describe('BufferReflow', () => {
|
|||||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语');
|
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('reflowLargerGetLinesToRemove', () => {
|
||||||
|
const nullCell = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||||
|
|
||||||
|
function createWrappedLines(chars: string): CircularList<IBufferLine> {
|
||||||
|
const lines = new CircularList<IBufferLine>(chars.length);
|
||||||
|
for (let i = 0; i < chars.length; i++) {
|
||||||
|
const line = new BufferLine(TEST_STRING_CACHE, 1);
|
||||||
|
line.set(0, [0, chars[i], 1, chars.charCodeAt(i)]);
|
||||||
|
line.isWrapped = i > 0;
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should skip reflow when the cursor is in a wrapped block and reflowCursorLine is false', () => {
|
||||||
|
const lines = createWrappedLines('abcde');
|
||||||
|
const skipped = reflowLargerGetLinesToRemove(lines, 1, 5, 2, nullCell, false);
|
||||||
|
const reflowed = reflowLargerGetLinesToRemove(lines, 1, 5, 2, nullCell, true);
|
||||||
|
assert.deepEqual(skipped, []);
|
||||||
|
assert.notDeepEqual(reflowed, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reflow wrapped blocks when the cursor is outside the block', () => {
|
||||||
|
const lines = createWrappedLines('abcde');
|
||||||
|
assert.notDeepEqual(reflowLargerGetLinesToRemove(lines, 1, 5, 10, nullCell, false), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { BufferLine } from './BufferLine';
|
import { BufferLine } from './BufferLine';
|
||||||
import { CircularList } from '../CircularList';
|
import { CircularList } from '../CircularList';
|
||||||
import { IBufferLine, ICellData } from '../Types';
|
import { IBufferLine, ICellData } from './Types';
|
||||||
|
|
||||||
export interface INewLayoutResult {
|
export interface INewLayoutResult {
|
||||||
layout: number[];
|
layout: number[];
|
||||||
|
|||||||
@@ -4,9 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Disposable, MutableDisposable } from '../Lifecycle';
|
import { Disposable, MutableDisposable } from '../Lifecycle';
|
||||||
import { IAttributeData } from '../Types';
|
|
||||||
import { Buffer } from './Buffer';
|
import { Buffer } from './Buffer';
|
||||||
import { IBuffer, IBufferSet } from './Types';
|
import { IAttributeData, IBuffer, IBufferSet } from './Types';
|
||||||
import { IBufferService, ILogService, IOptionsService } from '../services/Services';
|
import { IBufferService, ILogService, IOptionsService } from '../services/Services';
|
||||||
import { Emitter } from '../Event';
|
import { Emitter } from '../Event';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CharData, ICellData, IExtendedAttrs } from '../Types';
|
import { CharData, ICellData, IExtendedAttrs } from './Types';
|
||||||
import { stringFromCodePoint } from '../input/TextDecoder';
|
import { stringFromCodePoint } from '../input/TextDecoder';
|
||||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from './Constants';
|
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from './Constants';
|
||||||
import { AttributeData, ExtendedAttrs } from './AttributeData';
|
import { AttributeData, ExtendedAttrs } from './AttributeData';
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const NULL_CELL_CODE = 0;
|
|||||||
/**
|
/**
|
||||||
* Whitespace cell.
|
* Whitespace cell.
|
||||||
* This is meant as a replacement for empty cells when needed
|
* This is meant as a replacement for empty cells when needed
|
||||||
* during rendering lines to preserve correct aligment.
|
* during rendering lines to preserve correct alignment.
|
||||||
*/
|
*/
|
||||||
export const WHITESPACE_CELL_CHAR = ' ';
|
export const WHITESPACE_CELL_CHAR = ' ';
|
||||||
export const WHITESPACE_CELL_WIDTH = 1;
|
export const WHITESPACE_CELL_WIDTH = 1;
|
||||||
@@ -36,18 +36,18 @@ export const WHITESPACE_CELL_CODE = 32;
|
|||||||
export const enum Content {
|
export const enum Content {
|
||||||
/**
|
/**
|
||||||
* bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
|
* bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
|
||||||
* read: `codepoint = content & Content.codepointMask;`
|
* read: `codepoint = content & Content.CODEPOINT_MASK;`
|
||||||
* write: `content |= codepoint & Content.codepointMask;`
|
* write: `content |= codepoint & Content.CODEPOINT_MASK;`
|
||||||
* shortcut if precondition `codepoint <= 0x10FFFF` is met:
|
* shortcut if precondition `codepoint <= 0x10FFFF` is met:
|
||||||
* `content |= codepoint;`
|
* `content |= codepoint;`
|
||||||
*/
|
*/
|
||||||
CODEPOINT_MASK = 0x1FFFFF,
|
CODEPOINT_MASK = 0x1FFFFF,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bit 22 flag indication whether a cell contains combined content
|
* bit 22 flag indicating whether a cell contains combined content
|
||||||
* read: `isCombined = content & Content.isCombined;`
|
* read: `isCombined = content & Content.IS_COMBINED_MASK;`
|
||||||
* set: `content |= Content.isCombined;`
|
* set: `content |= Content.IS_COMBINED_MASK;`
|
||||||
* clear: `content &= ~Content.isCombined;`
|
* clear: `content &= ~Content.IS_COMBINED_MASK;`
|
||||||
*/
|
*/
|
||||||
IS_COMBINED_MASK = 0x200000, // 1 << 21
|
IS_COMBINED_MASK = 0x200000, // 1 << 21
|
||||||
|
|
||||||
@@ -55,19 +55,19 @@ export const enum Content {
|
|||||||
* bit 1..22 mask to check whether a cell contains any string data
|
* bit 1..22 mask to check whether a cell contains any string data
|
||||||
* we need to check for codepoint and isCombined bits to see
|
* we need to check for codepoint and isCombined bits to see
|
||||||
* whether a cell contains anything
|
* whether a cell contains anything
|
||||||
* read: `isEmpty = !(content & Content.hasContent)`
|
* read: `isEmpty = !(content & Content.HAS_CONTENT_MASK)`
|
||||||
*/
|
*/
|
||||||
HAS_CONTENT_MASK = 0x3FFFFF,
|
HAS_CONTENT_MASK = 0x3FFFFF,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
|
* bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
|
||||||
* read: `width = (content & Content.widthMask) >> Content.widthShift;`
|
* read: `width = (content & Content.WIDTH_MASK) >> Content.WIDTH_SHIFT;`
|
||||||
* `hasWidth = content & Content.widthMask;`
|
* `hasWidth = content & Content.WIDTH_MASK;`
|
||||||
* as long as wcwidth is highest value in `content`:
|
* as long as wcwidth is highest value in `content`:
|
||||||
* `width = content >> Content.widthShift;`
|
* `width = content >> Content.WIDTH_SHIFT;`
|
||||||
* write: `content |= (width << Content.widthShift) & Content.widthMask;`
|
* write: `content |= (width << Content.WIDTH_SHIFT) & Content.WIDTH_MASK;`
|
||||||
* shortcut if precondition `0 <= width <= 3` is met:
|
* shortcut if precondition `0 <= width <= 3` is met:
|
||||||
* `content |= width << Content.widthShift;`
|
* `content |= width << Content.WIDTH_SHIFT;`
|
||||||
*/
|
*/
|
||||||
WIDTH_MASK = 0xC00000, // 3 << 22
|
WIDTH_MASK = 0xC00000, // 3 << 22
|
||||||
WIDTH_SHIFT = 22
|
WIDTH_SHIFT = 22
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDisposable, IMarker } from '../Types';
|
import { dispose, IDisposable } from '../Lifecycle';
|
||||||
|
import { IMarker } from './Types';
|
||||||
import { Emitter } from '../Event';
|
import { Emitter } from '../Event';
|
||||||
import { dispose } from '../Lifecycle';
|
|
||||||
|
|
||||||
export class Marker implements IMarker {
|
export class Marker implements IMarker {
|
||||||
private static _nextId = 1;
|
private static _nextId = 1;
|
||||||
@@ -30,7 +30,7 @@ export class Marker implements IMarker {
|
|||||||
}
|
}
|
||||||
this.isDisposed = true;
|
this.isDisposed = true;
|
||||||
this.line = -1;
|
this.line = -1;
|
||||||
// Emit before super.dispose such that dispose listeners get a change to react
|
// Emit before super.dispose such that dispose listeners get a chance to react
|
||||||
this._onDispose.fire();
|
this._onDispose.fire();
|
||||||
dispose(this._disposables);
|
dispose(this._disposables);
|
||||||
this._disposables.length = 0;
|
this._disposables.length = 0;
|
||||||
|
|||||||
+147
-1
@@ -3,12 +3,158 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset, IDisposable } from '../Types';
|
|
||||||
import type { IEvent } from '../Event';
|
import type { IEvent } from '../Event';
|
||||||
|
import type { ICircularList } from '../CircularList';
|
||||||
|
import type { ICharset } from '../Types';
|
||||||
|
import type { IDisposable } from '../Lifecycle';
|
||||||
|
import type { UnderlineStyle } from './Constants';
|
||||||
|
|
||||||
// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
|
// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
|
||||||
export type BufferIndex = [number, number];
|
export type BufferIndex = [number, number];
|
||||||
|
|
||||||
|
export type CharData = [attr: number, char: string, width: number, code: number];
|
||||||
|
|
||||||
|
export interface IExtendedAttrs {
|
||||||
|
ext: number;
|
||||||
|
underlineStyle: UnderlineStyle;
|
||||||
|
underlineColor: number;
|
||||||
|
underlineVariantOffset: number;
|
||||||
|
urlId: number;
|
||||||
|
clone(): IExtendedAttrs;
|
||||||
|
isEmpty(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that represents all attributes of a cell.
|
||||||
|
*/
|
||||||
|
export interface IAttributeData {
|
||||||
|
/**
|
||||||
|
* "fg" is a 32-bit unsigned integer that stores the foreground color of the cell in the 24 least
|
||||||
|
* significant bits and additional flags in the remaining 8 bits.
|
||||||
|
*/
|
||||||
|
fg: number;
|
||||||
|
/**
|
||||||
|
* "bg" is a 32-bit unsigned integer that stores the background color of the cell in the 24 least
|
||||||
|
* significant bits and additional flags in the remaining 8 bits.
|
||||||
|
*/
|
||||||
|
bg: number;
|
||||||
|
/**
|
||||||
|
* "extended", aka "ext", stores extended attributes beyond those available in fg and bg. This
|
||||||
|
* data is optional on a cell and encodes less common data.
|
||||||
|
*/
|
||||||
|
extended: IExtendedAttrs;
|
||||||
|
|
||||||
|
clone(): IAttributeData;
|
||||||
|
|
||||||
|
// flags
|
||||||
|
isInverse(): number;
|
||||||
|
isBold(): number;
|
||||||
|
isUnderline(): number;
|
||||||
|
isBlink(): number;
|
||||||
|
isInvisible(): number;
|
||||||
|
isItalic(): number;
|
||||||
|
isDim(): number;
|
||||||
|
isStrikethrough(): number;
|
||||||
|
isProtected(): number;
|
||||||
|
isOverline(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color mode of the foreground color which determines how to decode {@link getFgColor},
|
||||||
|
* possible values include {@link Attributes.CM_DEFAULT}, {@link Attributes.CM_P16},
|
||||||
|
* {@link Attributes.CM_P256} and {@link Attributes.CM_RGB}.
|
||||||
|
*/
|
||||||
|
getFgColorMode(): number;
|
||||||
|
/**
|
||||||
|
* The color mode of the background color which determines how to decode {@link getBgColor},
|
||||||
|
* possible values include {@link Attributes.CM_DEFAULT}, {@link Attributes.CM_P16},
|
||||||
|
* {@link Attributes.CM_P256} and {@link Attributes.CM_RGB}.
|
||||||
|
*/
|
||||||
|
getBgColorMode(): number;
|
||||||
|
isFgRGB(): boolean;
|
||||||
|
isBgRGB(): boolean;
|
||||||
|
isFgPalette(): boolean;
|
||||||
|
isBgPalette(): boolean;
|
||||||
|
isFgDefault(): boolean;
|
||||||
|
isBgDefault(): boolean;
|
||||||
|
isAttributeDefault(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an integer representation of the foreground color, how to decode the color depends on the
|
||||||
|
* color mode {@link getFgColorMode}.
|
||||||
|
*/
|
||||||
|
getFgColor(): number;
|
||||||
|
/**
|
||||||
|
* Gets an integer representation of the background color, how to decode the color depends on the
|
||||||
|
* color mode {@link getBgColorMode}.
|
||||||
|
*/
|
||||||
|
getBgColor(): number;
|
||||||
|
|
||||||
|
// extended attrs
|
||||||
|
hasExtendedAttrs(): number;
|
||||||
|
updateExtended(): void;
|
||||||
|
getUnderlineColor(): number;
|
||||||
|
getUnderlineColorMode(): number;
|
||||||
|
isUnderlineColorRGB(): boolean;
|
||||||
|
isUnderlineColorPalette(): boolean;
|
||||||
|
isUnderlineColorDefault(): boolean;
|
||||||
|
getUnderlineStyle(): number;
|
||||||
|
getUnderlineVariantOffset(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cell data */
|
||||||
|
export interface ICellData extends IAttributeData {
|
||||||
|
content: number;
|
||||||
|
combinedData: string;
|
||||||
|
isCombined(): number;
|
||||||
|
getWidth(): number;
|
||||||
|
getChars(): string;
|
||||||
|
getCode(): number;
|
||||||
|
setFromCharData(value: CharData): void;
|
||||||
|
getAsCharData(): CharData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a line in the terminal buffer.
|
||||||
|
*/
|
||||||
|
export interface IBufferLine {
|
||||||
|
length: number;
|
||||||
|
isWrapped: boolean;
|
||||||
|
get(index: number): CharData;
|
||||||
|
set(index: number, value: CharData): void;
|
||||||
|
loadCell(index: number, cell: ICellData): ICellData;
|
||||||
|
setCell(index: number, cell: ICellData): void;
|
||||||
|
setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void;
|
||||||
|
addCodepointToCell(index: number, codePoint: number, width: number): void;
|
||||||
|
insertCells(pos: number, n: number, ch: ICellData): void;
|
||||||
|
deleteCells(pos: number, n: number, fill: ICellData): void;
|
||||||
|
replaceCells(start: number, end: number, fill: ICellData, respectProtect?: boolean): void;
|
||||||
|
resize(cols: number, fill: ICellData): boolean;
|
||||||
|
cleanupMemory(): number;
|
||||||
|
fill(fillCellData: ICellData, respectProtect?: boolean): void;
|
||||||
|
copyFrom(line: IBufferLine): void;
|
||||||
|
clone(): IBufferLine;
|
||||||
|
getTrimmedLength(): number;
|
||||||
|
getNoBgTrimmedLength(): number;
|
||||||
|
translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;
|
||||||
|
|
||||||
|
/* direct access to cell attrs */
|
||||||
|
getWidth(index: number): number;
|
||||||
|
hasWidth(index: number): number;
|
||||||
|
getFg(index: number): number;
|
||||||
|
getBg(index: number): number;
|
||||||
|
hasContent(index: number): number;
|
||||||
|
getCodePoint(index: number): number;
|
||||||
|
isCombined(index: number): number;
|
||||||
|
getString(index: number): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMarker extends IDisposable {
|
||||||
|
readonly id: number;
|
||||||
|
readonly isDisposed: boolean;
|
||||||
|
readonly line: number;
|
||||||
|
onDispose: IEvent<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IBuffer {
|
export interface IBuffer {
|
||||||
readonly lines: ICircularList<IBufferLine>;
|
readonly lines: ICircularList<IBufferLine>;
|
||||||
ydisp: number;
|
ydisp: number;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function evaluateKeyboardEvent(
|
|||||||
// Whether to cancel event propagation (NOTE: this may not be needed since the event is
|
// Whether to cancel event propagation (NOTE: this may not be needed since the event is
|
||||||
// canceled at the end of keyDown
|
// canceled at the end of keyDown
|
||||||
cancel: false,
|
cancel: false,
|
||||||
// The new key even to emit
|
// The new key event to emit
|
||||||
key: undefined
|
key: undefined
|
||||||
};
|
};
|
||||||
const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);
|
const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ function assertDecodedRange(
|
|||||||
}
|
}
|
||||||
const target = new Uint32Array(count);
|
const target = new Uint32Array(count);
|
||||||
const length = decode(input, target);
|
const length = decode(input, target);
|
||||||
assert.equal(length, count);
|
assert.strictEqual(length, count);
|
||||||
let mismatchIndex = -1;
|
let mismatchIndex = -1;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
for (let i = min; i < max; ++i) {
|
for (let i = min; i < max; ++i) {
|
||||||
@@ -92,8 +92,8 @@ function assertDecodedRange(
|
|||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
assert.equal(mismatchIndex, -1);
|
assert.strictEqual(mismatchIndex, -1);
|
||||||
assert.equal(outputToString(target, length), input);
|
assert.strictEqual(outputToString(target, length), input);
|
||||||
}
|
}
|
||||||
|
|
||||||
const BATCH_SIZE = 8192;
|
const BATCH_SIZE = 8192;
|
||||||
@@ -116,9 +116,9 @@ describe('text encodings', () => {
|
|||||||
const data = new Uint32Array(s.length);
|
const data = new Uint32Array(s.length);
|
||||||
for (let i = 0; i < s.length; ++i) {
|
for (let i = 0; i < s.length; ++i) {
|
||||||
data[i] = s.charCodeAt(i);
|
data[i] = s.charCodeAt(i);
|
||||||
assert.equal(stringFromCodePoint(data[i]), s[i]);
|
assert.strictEqual(stringFromCodePoint(data[i]), s[i]);
|
||||||
}
|
}
|
||||||
assert.equal(utf32ToString(data), s);
|
assert.strictEqual(utf32ToString(data), s);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('StringToUtf32 decoder', () => {
|
describe('StringToUtf32 decoder', () => {
|
||||||
@@ -159,7 +159,7 @@ describe('text encodings', () => {
|
|||||||
const decoder = new StringToUtf32();
|
const decoder = new StringToUtf32();
|
||||||
const target = new Uint32Array(5);
|
const target = new Uint32Array(5);
|
||||||
const length = decoder.decode(String.fromCharCode(0xFEFF), target);
|
const length = decoder.decode(String.fromCharCode(0xFEFF), target);
|
||||||
assert.equal(length, 0);
|
assert.strictEqual(length, 0);
|
||||||
decoder.clear();
|
decoder.clear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -169,7 +169,7 @@ describe('text encodings', () => {
|
|||||||
const target = new Uint32Array(500);
|
const target = new Uint32Array(500);
|
||||||
for (let i = 0; i < TEST_STRINGS.length; ++i) {
|
for (let i = 0; i < TEST_STRINGS.length; ++i) {
|
||||||
const length = decoder.decode(TEST_STRINGS[i], target);
|
const length = decoder.decode(TEST_STRINGS[i], target);
|
||||||
assert.equal(toString(target, length), TEST_STRINGS[i]);
|
assert.strictEqual(toString(target, length), TEST_STRINGS[i]);
|
||||||
decoder.clear();
|
decoder.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -184,7 +184,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(input[i], target);
|
const written = decoder.decode(input[i], target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -226,7 +226,7 @@ describe('text encodings', () => {
|
|||||||
const target = new Uint32Array(5);
|
const target = new Uint32Array(5);
|
||||||
const utf8Data = stringToUtf8Bytes(String.fromCharCode(0xFEFF));
|
const utf8Data = stringToUtf8Bytes(String.fromCharCode(0xFEFF));
|
||||||
const length = decoder.decode(utf8Data, target);
|
const length = decoder.decode(utf8Data, target);
|
||||||
assert.equal(length, 0);
|
assert.strictEqual(length, 0);
|
||||||
decoder.clear();
|
decoder.clear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -237,7 +237,7 @@ describe('text encodings', () => {
|
|||||||
for (let i = 0; i < TEST_STRINGS.length; ++i) {
|
for (let i = 0; i < TEST_STRINGS.length; ++i) {
|
||||||
const utf8Data = stringToUtf8Bytes(TEST_STRINGS[i]);
|
const utf8Data = stringToUtf8Bytes(TEST_STRINGS[i]);
|
||||||
const length = decoder.decode(utf8Data, target);
|
const length = decoder.decode(utf8Data, target);
|
||||||
assert.equal(toString(target, length), TEST_STRINGS[i]);
|
assert.strictEqual(toString(target, length), TEST_STRINGS[i]);
|
||||||
decoder.clear();
|
decoder.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -252,7 +252,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert(decoded, 'ÄÖÜßöäü');
|
assert.strictEqual(decoded, 'ÄÖÜßöäü');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('2/3 byte sequences - advance by 1', () => {
|
it('2/3 byte sequences - advance by 1', () => {
|
||||||
@@ -264,7 +264,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert(decoded, 'Āր܀߀ö€ä€ü');
|
assert.strictEqual(decoded, 'Āր܀߀ö€ä€ü');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('2/3/4 byte sequences - advance by 1', () => {
|
it('2/3/4 byte sequences - advance by 1', () => {
|
||||||
@@ -276,7 +276,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('2/3/4 byte sequences - advance by 2', () => {
|
it('2/3/4 byte sequences - advance by 2', () => {
|
||||||
@@ -288,7 +288,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
|
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('2/3/4 byte sequences - advance by 3', () => {
|
it('2/3/4 byte sequences - advance by 3', () => {
|
||||||
@@ -300,7 +300,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(utf8Data.slice(i, i + 3), target);
|
const written = decoder.decode(utf8Data.slice(i, i + 3), target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('BOMs (3 byte sequences) - advance by 2', () => {
|
it('BOMs (3 byte sequences) - advance by 2', () => {
|
||||||
@@ -312,7 +312,7 @@ describe('text encodings', () => {
|
|||||||
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
|
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
|
||||||
decoded += toString(target, written);
|
decoded += toString(target, written);
|
||||||
}
|
}
|
||||||
assert.equal(decoded, '');
|
assert.strictEqual(decoded, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test break after 3 bytes - issue #2495', () => {
|
it('test break after 3 bytes - issue #2495', () => {
|
||||||
@@ -320,11 +320,37 @@ describe('text encodings', () => {
|
|||||||
const target = new Uint32Array(5);
|
const target = new Uint32Array(5);
|
||||||
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
|
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
|
||||||
let written = decoder.decode(utf8Data.slice(0, 3), target);
|
let written = decoder.decode(utf8Data.slice(0, 3), target);
|
||||||
assert.equal(written, 0);
|
assert.strictEqual(written, 0);
|
||||||
written = decoder.decode(utf8Data.slice(3), target);
|
written = decoder.decode(utf8Data.slice(3), target);
|
||||||
assert.equal(written, 1);
|
assert.strictEqual(written, 1);
|
||||||
assert(toString(target, written), '𠜎');
|
assert.strictEqual(toString(target, written), '𠜎');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('0x80 not swallowed in continuation', () => {
|
||||||
|
it('A—B', () => {
|
||||||
|
const decoder = new Utf8ToUtf32();
|
||||||
|
const target = new Uint32Array(5);
|
||||||
|
const utf8Data = new TextEncoder().encode('A—BA—BA—BA—BA—B');
|
||||||
|
let decoded = '';
|
||||||
|
for (let i = 0; i < utf8Data.length; i += 2) {
|
||||||
|
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
|
||||||
|
decoded += toString(target, written);
|
||||||
|
}
|
||||||
|
assert.strictEqual(decoded, 'A—BA—BA—BA—BA—B');
|
||||||
|
});
|
||||||
|
it('A𐀀B', () => {
|
||||||
|
const decoder = new Utf8ToUtf32();
|
||||||
|
const target = new Uint32Array(5);
|
||||||
|
const utf8Data = new TextEncoder().encode('A𐀀BA𐀀BA𐀀BA𐀀BA𐀀B');
|
||||||
|
let decoded = '';
|
||||||
|
for (let i = 0; i < utf8Data.length; i += 2) {
|
||||||
|
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
|
||||||
|
decoded += toString(target, written);
|
||||||
|
}
|
||||||
|
assert.strictEqual(decoded, 'A𐀀BA𐀀BA𐀀BA𐀀BA𐀀B');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -158,9 +158,9 @@ export class Utf8ToUtf32 {
|
|||||||
cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);
|
cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
let tmp: number;
|
let tmp: number;
|
||||||
while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) {
|
while ((tmp = this.interim[++pos]) && pos < 4) {
|
||||||
cp <<= 6;
|
cp <<= 6;
|
||||||
cp |= tmp;
|
cp |= tmp & 0x3F;
|
||||||
}
|
}
|
||||||
// missing bytes - read ahead from input
|
// missing bytes - read ahead from input
|
||||||
const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;
|
const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export class UnicodeV6 implements IUnicodeVersionProvider {
|
|||||||
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
|
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
|
||||||
let width = this.wcwidth(codepoint);
|
let width = this.wcwidth(codepoint);
|
||||||
let shouldJoin = width === 0 && preceding !== 0;
|
let shouldJoin = width === 0 && preceding !== 0;
|
||||||
|
// HACK: Ideally this file would not depend on the service which uses it
|
||||||
if (shouldJoin) {
|
if (shouldJoin) {
|
||||||
const oldWidth = UnicodeService.extractWidth(preceding);
|
const oldWidth = UnicodeService.extractWidth(preceding);
|
||||||
if (oldWidth === 0) {
|
if (oldWidth === 0) {
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ export class WriteBuffer extends Disposable {
|
|||||||
* this condition here, also the renderer has no way to spot nonsense updates either.
|
* this condition here, also the renderer has no way to spot nonsense updates either.
|
||||||
* FIXME: A proper fix for this would track the FPS at the renderer entry level separately.
|
* FIXME: A proper fix for this would track the FPS at the renderer entry level separately.
|
||||||
*
|
*
|
||||||
* If favoring of FPS shows bad throughtput impact, use the following instead. It favors
|
* If favoring of FPS shows bad throughput impact, use the following instead. It favors
|
||||||
* throughput by eval'ing `startTime` upfront pulling at least one more chunk into the
|
* throughput by eval'ing `startTime` upfront pulling at least one more chunk into the
|
||||||
* current microtask queue (executed before setTimeout).
|
* current microtask queue (executed before setTimeout).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function identifier(id: IFunctionIdentifier): number {
|
|||||||
throw new Error('only one byte as prefix supported');
|
throw new Error('only one byte as prefix supported');
|
||||||
}
|
}
|
||||||
res = id.prefix.charCodeAt(0);
|
res = id.prefix.charCodeAt(0);
|
||||||
if (res && 0x3c > res || res > 0x3f) {
|
if (res < 0x3c || res > 0x3f) {
|
||||||
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,4 +459,29 @@ describe('ApcParser - async tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('reset', () => {
|
||||||
|
it('should abort active handlers with end(false) when reset during payload', () => {
|
||||||
|
const ident = identifier({ intermediates: '+', final: 'p' });
|
||||||
|
parser.registerHandler(ident, new TestHandler(reports, 'th'));
|
||||||
|
parser.start(ident);
|
||||||
|
let data = toUtf32('partial');
|
||||||
|
parser.put(data, 0, data.length);
|
||||||
|
parser.reset();
|
||||||
|
assert.deepEqual(reports, [
|
||||||
|
['th', 'START'],
|
||||||
|
['th', 'PUT', 'partial'],
|
||||||
|
['th', 'END', false]
|
||||||
|
]);
|
||||||
|
reports.length = 0;
|
||||||
|
parser.start(ident);
|
||||||
|
data = toUtf32('complete');
|
||||||
|
parser.put(data, 0, data.length);
|
||||||
|
parser.end(true);
|
||||||
|
assert.deepEqual(reports, [
|
||||||
|
['th', 'START'],
|
||||||
|
['th', 'PUT', 'complete'],
|
||||||
|
['th', 'END', true]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function identifier(id: IFunctionIdentifier): number {
|
|||||||
throw new Error('only one byte as prefix supported');
|
throw new Error('only one byte as prefix supported');
|
||||||
}
|
}
|
||||||
res = id.prefix.charCodeAt(0);
|
res = id.prefix.charCodeAt(0);
|
||||||
if (res && 0x3c > res || res > 0x3f) {
|
if (res < 0x3c || res > 0x3f) {
|
||||||
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,4 +470,30 @@ describe('DcsParser - async tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('reset', () => {
|
||||||
|
it('should abort active handlers with unhook(false) when reset during payload', () => {
|
||||||
|
const ident = identifier({ intermediates: '+', final: 'p' });
|
||||||
|
const params = Params.fromArray([1, 2, 3]);
|
||||||
|
parser.registerHandler(ident, new TestHandler(reports, 'th'));
|
||||||
|
parser.hook(ident, params);
|
||||||
|
let data = toUtf32('partial');
|
||||||
|
parser.put(data, 0, data.length);
|
||||||
|
parser.reset();
|
||||||
|
assert.deepEqual(reports, [
|
||||||
|
['th', 'HOOK', [1, 2, 3]],
|
||||||
|
['th', 'PUT', 'partial'],
|
||||||
|
['th', 'UNHOOK', false]
|
||||||
|
]);
|
||||||
|
reports.length = 0;
|
||||||
|
parser.hook(ident, params);
|
||||||
|
data = toUtf32('complete');
|
||||||
|
parser.put(data, 0, data.length);
|
||||||
|
parser.unhook(true);
|
||||||
|
assert.deepEqual(reports, [
|
||||||
|
['th', 'HOOK', [1, 2, 3]],
|
||||||
|
['th', 'PUT', 'complete'],
|
||||||
|
['th', 'UNHOOK', true]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ describe('EscapeSequenceParser', () => {
|
|||||||
p = new TestEscapeSequenceParser(tansitions);
|
p = new TestEscapeSequenceParser(tansitions);
|
||||||
assert.deepEqual(p.transitions, tansitions);
|
assert.deepEqual(p.transitions, tansitions);
|
||||||
});
|
});
|
||||||
it('inital states', () => {
|
it('initial states', () => {
|
||||||
assert.equal(parser.initialState, ParserState.GROUND);
|
assert.equal(parser.initialState, ParserState.GROUND);
|
||||||
assert.equal(parser.currentState, ParserState.GROUND);
|
assert.equal(parser.currentState, ParserState.GROUND);
|
||||||
assert.equal(parser.osc, '');
|
assert.equal(parser.osc, '');
|
||||||
@@ -2177,6 +2177,38 @@ describe('EscapeSequenceParser - async', () => {
|
|||||||
parser.reset();
|
parser.reset();
|
||||||
await parseP(parser, INPUT); // does not throw anymore
|
await parseP(parser, INPUT); // does not throw anymore
|
||||||
});
|
});
|
||||||
|
it('reset during async pause continues at next codepoint in chunk', async () => {
|
||||||
|
const localParser = new TestEscapeSequenceParser();
|
||||||
|
const localStack: any[] = [];
|
||||||
|
localParser.setPrintHandler((data, start, end) => {
|
||||||
|
let result = '';
|
||||||
|
for (let i = start; i < end; ++i) {
|
||||||
|
result += stringFromCodePoint(data[i]);
|
||||||
|
}
|
||||||
|
localStack.push(['PRINT', result]);
|
||||||
|
});
|
||||||
|
localParser.registerCsiHandler({ final: 'm' }, async params => {
|
||||||
|
localStack.push(['SGR', params.toArray()]);
|
||||||
|
return new Promise<boolean>(resolve => setTimeout(() => resolve(true), 0));
|
||||||
|
});
|
||||||
|
const data = '\x1b[1mXY';
|
||||||
|
const container = new Uint32Array(data.length);
|
||||||
|
const decoder = new StringToUtf32();
|
||||||
|
const len = decoder.decode(data, container);
|
||||||
|
|
||||||
|
assert.ok(localParser.parse(container, len) instanceof Promise);
|
||||||
|
localParser.reset();
|
||||||
|
assert.equal(localParser.parseStack.state, ParserStackType.RESET);
|
||||||
|
|
||||||
|
let prev: boolean | undefined = true;
|
||||||
|
let result: void | Promise<boolean> | undefined;
|
||||||
|
while (result = localParser.parse(container, len, prev)) {
|
||||||
|
prev = await result;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(localStack, [['SGR', [1]], ['PRINT', 'XY']]);
|
||||||
|
assert.equal(localParser.parseStack.state, ParserStackType.NONE);
|
||||||
|
});
|
||||||
it('correct result on awaited parse call', async () => {
|
it('correct result on awaited parse call', async () => {
|
||||||
await parseP(parser, INPUT);
|
await parseP(parser, INPUT);
|
||||||
assert.deepEqual(callstack, RESULT);
|
assert.deepEqual(callstack, RESULT);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { ApcParser } from './ApcParser';
|
|||||||
// @vt: #Y ESC CSI "Control Sequence Introducer" "ESC [" "Start of a CSI sequence."
|
// @vt: #Y ESC CSI "Control Sequence Introducer" "ESC [" "Start of a CSI sequence."
|
||||||
// @vt: #Y ESC OSC "Operating System Command" "ESC ]" "Start of an OSC sequence."
|
// @vt: #Y ESC OSC "Operating System Command" "ESC ]" "Start of an OSC sequence."
|
||||||
// @vt: #Y ESC DCS "Device Control String" "ESC P" "Start of a DCS sequence."
|
// @vt: #Y ESC DCS "Device Control String" "ESC P" "Start of a DCS sequence."
|
||||||
// @vt: #Y ESC ST "String Terminator" "ESC \" "Terminator used for string type sequences."
|
// @vt: #Y ESC ST "String Terminator" "ESC \\" "Terminator used for string type sequences."
|
||||||
// @vt: #Y ESC PM "Privacy Message" "ESC ^" "Start of a privacy message."
|
// @vt: #Y ESC PM "Privacy Message" "ESC ^" "Start of a privacy message."
|
||||||
// @vt: #Y ESC APC "Application Program Command" "ESC _" "Start of an APC sequence."
|
// @vt: #Y ESC APC "Application Program Command" "ESC _" "Start of an APC sequence."
|
||||||
// @vt: #Y C1 CSI "Control Sequence Introducer" "\x9B" "Start of a CSI sequence."
|
// @vt: #Y C1 CSI "Control Sequence Introducer" "\x9B" "Start of a CSI sequence."
|
||||||
@@ -243,7 +243,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
|||||||
*
|
*
|
||||||
* This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
|
* This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
|
||||||
* as suggested by the original parser, thus empty parameters are set to 0.
|
* as suggested by the original parser, thus empty parameters are set to 0.
|
||||||
* This this is not in line with the latest ECMA-48 specification
|
* This is not in line with the latest ECMA-48 specification
|
||||||
* (ZDM was part of the early specs and got completely removed later on).
|
* (ZDM was part of the early specs and got completely removed later on).
|
||||||
*
|
*
|
||||||
* Other than the original parser from vt100.net this parser supports
|
* Other than the original parser from vt100.net this parser supports
|
||||||
@@ -342,7 +342,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
throw new Error('only one byte as prefix supported');
|
throw new Error('only one byte as prefix supported');
|
||||||
}
|
}
|
||||||
res = id.prefix.charCodeAt(0);
|
res = id.prefix.charCodeAt(0);
|
||||||
if (res && 0x3c > res || res > 0x3f) {
|
if (res < 0x3c || res > 0x3f) {
|
||||||
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,4 +464,28 @@ describe('OscParser - async tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('reset', () => {
|
||||||
|
it('should abort active handlers with end(false) when reset during payload', () => {
|
||||||
|
parser.registerHandler(1234, new TestHandler(1234, reports, 'th'));
|
||||||
|
parser.start();
|
||||||
|
let data = toUtf32('1234;partial');
|
||||||
|
parser.put(data, 0, data.length);
|
||||||
|
parser.reset();
|
||||||
|
assert.deepEqual(reports, [
|
||||||
|
['th', 1234, 'START'],
|
||||||
|
['th', 1234, 'PUT', 'partial'],
|
||||||
|
['th', 1234, 'END', false]
|
||||||
|
]);
|
||||||
|
reports.length = 0;
|
||||||
|
parser.start();
|
||||||
|
data = toUtf32('1234;complete');
|
||||||
|
parser.put(data, 0, data.length);
|
||||||
|
parser.end(true);
|
||||||
|
assert.deepEqual(reports, [
|
||||||
|
['th', 1234, 'START'],
|
||||||
|
['th', 1234, 'PUT', 'complete'],
|
||||||
|
['th', 1234, 'END', true]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -199,13 +199,13 @@ describe('Params', () => {
|
|||||||
it('reject params lesser -1', () => {
|
it('reject params lesser -1', () => {
|
||||||
const params = new Params();
|
const params = new Params();
|
||||||
params.addParam(-1);
|
params.addParam(-1);
|
||||||
assert.throws(() => params.addParam(-2), 'values lesser than -1 are not allowed');
|
assert.throws(() => params.addParam(-2), 'values less than -1 are not allowed');
|
||||||
});
|
});
|
||||||
it('reject subparams lesser -1', () => {
|
it('reject subparams lesser -1', () => {
|
||||||
const params = new Params();
|
const params = new Params();
|
||||||
params.addParam(-1);
|
params.addParam(-1);
|
||||||
params.addSubParam(-1);
|
params.addSubParam(-1);
|
||||||
assert.throws(() => params.addSubParam(-2), 'values lesser than -1 are not allowed');
|
assert.throws(() => params.addSubParam(-2), 'values less than -1 are not allowed');
|
||||||
assert.deepEqual(params.toArray(), [-1, [-1]]);
|
assert.deepEqual(params.toArray(), [-1, [-1]]);
|
||||||
});
|
});
|
||||||
it('clamp parsed params', () => {
|
it('clamp parsed params', () => {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export class Params implements IParams {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value < -1) {
|
if (value < -1) {
|
||||||
throw new Error('values lesser than -1 are not allowed');
|
throw new Error('values less than -1 are not allowed');
|
||||||
}
|
}
|
||||||
this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength;
|
this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength;
|
||||||
this.params[this.length++] = value > Constants.MAX_VALUE ? Constants.MAX_VALUE : value;
|
this.params[this.length++] = value > Constants.MAX_VALUE ? Constants.MAX_VALUE : value;
|
||||||
@@ -172,7 +172,7 @@ export class Params implements IParams {
|
|||||||
* Add a sub parameter value.
|
* Add a sub parameter value.
|
||||||
* The sub parameter is automatically associated with the last parameter value.
|
* The sub parameter is automatically associated with the last parameter value.
|
||||||
* Thus it is not possible to add a subparameter without any parameter added yet.
|
* Thus it is not possible to add a subparameter without any parameter added yet.
|
||||||
* `Params` only stores up to `subParamsLength` sub parameters, any later
|
* `Params` only stores up to `maxSubParamsLength` sub parameters, any later
|
||||||
* sub parameter will be ignored.
|
* sub parameter will be ignored.
|
||||||
*/
|
*/
|
||||||
public addSubParam(value: number): void {
|
public addSubParam(value: number): void {
|
||||||
@@ -185,7 +185,7 @@ export class Params implements IParams {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value < -1) {
|
if (value < -1) {
|
||||||
throw new Error('values lesser than -1 are not allowed');
|
throw new Error('values less than -1 are not allowed');
|
||||||
}
|
}
|
||||||
this._subParams[this._subParamsLength++] = value > Constants.MAX_VALUE ? Constants.MAX_VALUE : value;
|
this._subParams[this._subParamsLength++] = value > Constants.MAX_VALUE ? Constants.MAX_VALUE : value;
|
||||||
this._subParamsIdx[this.length - 1]++;
|
this._subParamsIdx[this.length - 1]++;
|
||||||
|
|||||||
@@ -3,12 +3,10 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDisposable } from '../Types';
|
import { IDisposable, IParams, ParamsArray } from '../Types';
|
||||||
import { ParserState } from './Constants';
|
import { ParserState } from './Constants';
|
||||||
|
|
||||||
|
export type { IParams, ParamsArray };
|
||||||
/** sequence params serialized to js arrays */
|
|
||||||
export type ParamsArray = (number | number[])[];
|
|
||||||
|
|
||||||
/** Params constructor type. */
|
/** Params constructor type. */
|
||||||
export interface IParamsConstructor {
|
export interface IParamsConstructor {
|
||||||
@@ -18,28 +16,6 @@ export interface IParamsConstructor {
|
|||||||
fromArray(values: ParamsArray): IParams;
|
fromArray(values: ParamsArray): IParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Interface of Params storage class. */
|
|
||||||
export interface IParams {
|
|
||||||
/** from ctor */
|
|
||||||
maxLength: number;
|
|
||||||
maxSubParamsLength: number;
|
|
||||||
|
|
||||||
/** param values and its length */
|
|
||||||
params: Int32Array;
|
|
||||||
length: number;
|
|
||||||
|
|
||||||
/** methods */
|
|
||||||
clone(): IParams;
|
|
||||||
toArray(): ParamsArray;
|
|
||||||
reset(): void;
|
|
||||||
resetZdm(): void;
|
|
||||||
addParam(value: number): void;
|
|
||||||
addSubParam(value: number): void;
|
|
||||||
hasSubParams(idx: number): boolean;
|
|
||||||
getSubParams(idx: number): Int32Array | null;
|
|
||||||
getSubParamsAll(): {[idx: number]: Int32Array};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal state of EscapeSequenceParser.
|
* Internal state of EscapeSequenceParser.
|
||||||
* Used as argument of the error handler to allow
|
* Used as argument of the error handler to allow
|
||||||
@@ -258,8 +234,8 @@ export interface IApcParser extends ISubParser<IApcHandler, ApcFallbackHandlerTy
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to denote a specific ESC, CSI or DCS handler slot.
|
* Interface to denote a specific ESC, CSI or DCS handler slot.
|
||||||
* The values are used to create an integer respresentation during handler
|
* The values are used to create an integer representation during handler
|
||||||
* regristation before passed to the subparsers as `ident`.
|
* registration before passed to the subparsers as `ident`.
|
||||||
* The integer translation is made to allow a faster handler access
|
* The integer translation is made to allow a faster handler access
|
||||||
* in `EscapeSequenceParser.parse`.
|
* in `EscapeSequenceParser.parse`.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CellData } from '../buffer/CellData';
|
import { CellData } from '../buffer/CellData';
|
||||||
import { IBufferLine, ICellData } from '../Types';
|
import { IBufferLine, ICellData } from '../buffer/Types';
|
||||||
import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from '@xterm/xterm';
|
import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from '@xterm/xterm';
|
||||||
|
|
||||||
export class BufferLineApiView implements IBufferLineApi {
|
export class BufferLineApiView implements IBufferLineApi {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user