Compare 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 | ||
|
|
936442c813 | ||
|
|
dfb65ca66f | ||
|
|
35c91cbf2d | ||
|
|
587856e2ca | ||
|
|
56a7195487 | ||
|
|
4aa04c6b3f | ||
|
|
28eadddb79 | ||
|
|
59d1ec7bf0 | ||
|
|
7c82c1a815 | ||
|
|
04a0b7347c | ||
|
|
5d8da19c44 | ||
|
|
17ad33dc3d | ||
|
|
f76c7aa8ea | ||
|
|
43e8365f0e | ||
|
|
c7a94fc0bc | ||
|
|
4bb9c06b5f | ||
|
|
00e54532b3 | ||
|
|
e96133603b | ||
|
|
b2003861ec | ||
|
|
38f77fd4ca | ||
|
|
d3352cf1d5 | ||
|
|
0c4ec34df8 | ||
|
|
743852b60c | ||
|
|
2069176528 | ||
|
|
acd2472961 | ||
|
|
1b95efa8c8 | ||
|
|
ce1f5f9608 | ||
|
|
bf8af4a871 | ||
|
|
f8e9a81d0e | ||
|
|
7711d780c2 | ||
|
|
f8cef535c7 | ||
|
|
5ad56446b8 | ||
|
|
fbb2a9710f | ||
|
|
5a6427e867 | ||
|
|
8519ac6d99 | ||
|
|
ae611694de | ||
|
|
772df0cb98 | ||
|
|
aa93189cc5 | ||
|
|
f82a8029d2 | ||
|
|
a2f321c887 | ||
|
|
eb58cefa8e | ||
|
|
d92ca87cbe | ||
|
|
224167bbbe | ||
|
|
740fa44ae4 | ||
|
|
636baa8d79 | ||
|
|
44a27dc958 | ||
|
|
dc1ae4514b | ||
|
|
494cf1f2e1 | ||
|
|
645865efec | ||
|
|
83e3657a82 | ||
|
|
53554a05f7 | ||
|
|
51c8d8abf8 | ||
|
|
05ea5ac324 | ||
|
|
9e27c7debb |
+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'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ docs/
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
/.idea/
|
/.idea/
|
||||||
.env
|
.env
|
||||||
|
mise.toml
|
||||||
build/
|
build/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|||||||
+374
@@ -0,0 +1,374 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": [
|
||||||
|
"import",
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"categories": {
|
||||||
|
"correctness": "off"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"typeAware": true,
|
||||||
|
"maxWarnings": 0
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"builtin": true
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"addons/*/src/third-party/*.ts",
|
||||||
|
"**/out/*",
|
||||||
|
"**/out-test/*",
|
||||||
|
"**/out-esbuild/*",
|
||||||
|
"**/out-esbuild-test/*",
|
||||||
|
"**/inwasm-sdks/*",
|
||||||
|
"**/typings/*.d.ts",
|
||||||
|
"**/node_modules",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.mjs"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"constructor-super": "error",
|
||||||
|
"for-direction": "error",
|
||||||
|
"getter-return": "error",
|
||||||
|
"no-async-promise-executor": "error",
|
||||||
|
"no-case-declarations": "error",
|
||||||
|
"no-class-assign": "error",
|
||||||
|
"no-compare-neg-zero": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-const-assign": "error",
|
||||||
|
"no-constant-binary-expression": "error",
|
||||||
|
"no-constant-condition": "error",
|
||||||
|
"no-control-regex": "error",
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-delete-var": "error",
|
||||||
|
"no-dupe-class-members": "error",
|
||||||
|
"no-dupe-else-if": "error",
|
||||||
|
"no-dupe-keys": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-empty": "error",
|
||||||
|
"no-empty-character-class": "error",
|
||||||
|
"no-empty-pattern": "error",
|
||||||
|
"no-empty-static-block": "error",
|
||||||
|
"no-ex-assign": "error",
|
||||||
|
"no-extra-boolean-cast": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-func-assign": "error",
|
||||||
|
"no-global-assign": "error",
|
||||||
|
"no-import-assign": "error",
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-loss-of-precision": "error",
|
||||||
|
"no-misleading-character-class": "error",
|
||||||
|
"no-new-native-nonconstructor": "error",
|
||||||
|
"no-nonoctal-decimal-escape": "error",
|
||||||
|
"no-obj-calls": "error",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
"no-redeclare": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-self-assign": "error",
|
||||||
|
"no-setter-return": "error",
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-sparse-arrays": "error",
|
||||||
|
"no-this-before-super": "error",
|
||||||
|
"no-unassigned-vars": "error",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-unreachable": "error",
|
||||||
|
"no-unsafe-finally": "error",
|
||||||
|
"no-unsafe-negation": "error",
|
||||||
|
"no-unsafe-optional-chaining": "error",
|
||||||
|
"no-unused-labels": "error",
|
||||||
|
"no-unused-private-class-members": "error",
|
||||||
|
"no-unused-vars": "error",
|
||||||
|
"no-useless-backreference": "error",
|
||||||
|
"no-useless-catch": "error",
|
||||||
|
"no-useless-escape": "error",
|
||||||
|
"no-with": "error",
|
||||||
|
"preserve-caught-error": "error",
|
||||||
|
"require-yield": "error",
|
||||||
|
"use-isnan": "error",
|
||||||
|
"valid-typeof": "error",
|
||||||
|
"no-array-constructor": "error",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
|
"typescript/ban-ts-comment": "error",
|
||||||
|
"typescript/no-duplicate-enum-values": "error",
|
||||||
|
"typescript/no-empty-object-type": "error",
|
||||||
|
"typescript/no-explicit-any": "error",
|
||||||
|
"typescript/no-extra-non-null-assertion": "error",
|
||||||
|
"typescript/no-misused-new": "error",
|
||||||
|
"typescript/no-namespace": "error",
|
||||||
|
"typescript/no-non-null-asserted-optional-chain": "error",
|
||||||
|
"typescript/no-require-imports": "error",
|
||||||
|
"typescript/no-this-alias": "error",
|
||||||
|
"typescript/no-unnecessary-type-constraint": "error",
|
||||||
|
"typescript/no-unsafe-declaration-merging": "error",
|
||||||
|
"typescript/no-unsafe-function-type": "error",
|
||||||
|
"typescript/no-wrapper-object-types": "error",
|
||||||
|
"typescript/prefer-as-const": "error",
|
||||||
|
"typescript/prefer-namespace-keyword": "error",
|
||||||
|
"typescript/triple-slash-reference": "error",
|
||||||
|
"import/no-cycle": "error"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.mts",
|
||||||
|
"**/*.cts"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"constructor-super": "off",
|
||||||
|
"getter-return": "off",
|
||||||
|
"no-class-assign": "off",
|
||||||
|
"no-const-assign": "off",
|
||||||
|
"no-dupe-class-members": "off",
|
||||||
|
"no-dupe-keys": "off",
|
||||||
|
"no-func-assign": "off",
|
||||||
|
"no-import-assign": "off",
|
||||||
|
"no-new-native-nonconstructor": "off",
|
||||||
|
"no-obj-calls": "off",
|
||||||
|
"no-redeclare": "off",
|
||||||
|
"no-setter-return": "off",
|
||||||
|
"no-this-before-super": "off",
|
||||||
|
"no-unreachable": "off",
|
||||||
|
"no-unsafe-negation": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"no-with": "off",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-rest-params": "error",
|
||||||
|
"prefer-spread": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*.ts"
|
||||||
|
],
|
||||||
|
"jsPlugins": [
|
||||||
|
"@stylistic/eslint-plugin",
|
||||||
|
{
|
||||||
|
"name": "jsdoc-js",
|
||||||
|
"specifier": "eslint-plugin-jsdoc"
|
||||||
|
},
|
||||||
|
"oxlint-plugin-eslint"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@stylistic/comma-spacing": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"before": false,
|
||||||
|
"after": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/indent": [
|
||||||
|
"warn",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"@stylistic/semi": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@stylistic/quotes": [
|
||||||
|
"warn",
|
||||||
|
"single",
|
||||||
|
{
|
||||||
|
"allowTemplateLiterals": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/member-delimiter-style": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"multiline": {
|
||||||
|
"delimiter": "semi",
|
||||||
|
"requireLast": true
|
||||||
|
},
|
||||||
|
"singleline": {
|
||||||
|
"delimiter": "comma",
|
||||||
|
"requireLast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/type-annotation-spacing": "warn",
|
||||||
|
"@stylistic/space-unary-ops": "warn",
|
||||||
|
"@stylistic/keyword-spacing": "warn",
|
||||||
|
"@stylistic/max-len": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"code": 1000,
|
||||||
|
"comments": 100,
|
||||||
|
"ignoreTrailingComments": true,
|
||||||
|
"ignoreUrls": true,
|
||||||
|
"ignorePattern": "^ *((?<vt_comment>(//|\\*) @vt)|(?<table_comment>\\* \\| )|(?<commented_code>// ))"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/new-parens": "warn",
|
||||||
|
"@stylistic/no-extra-semi": "error",
|
||||||
|
"@stylistic/no-trailing-spaces": "warn",
|
||||||
|
"eslint-js/object-curly-spacing": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@stylistic/spaced-comment": [
|
||||||
|
"warn",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"markers": [
|
||||||
|
"/"
|
||||||
|
],
|
||||||
|
"exceptions": [
|
||||||
|
"-"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jsdoc-js/check-alignment": "warn",
|
||||||
|
"jsdoc-js/check-param-names": "warn",
|
||||||
|
"jsdoc-js/multiline-blocks": "warn",
|
||||||
|
"jsdoc-js/no-multi-asterisks": "warn",
|
||||||
|
"eslint-js/one-var": [
|
||||||
|
"warn",
|
||||||
|
"never"
|
||||||
|
],
|
||||||
|
"eslint-js/no-restricted-syntax": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"selector": "CallExpression[callee.name='requestAnimationFrame']",
|
||||||
|
"message": "The global requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "CallExpression[callee.name='cancelAnimationFrame']",
|
||||||
|
"message": "The global cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "CallExpression > MemberExpression[object.name='window'][property.name='requestAnimationFrame']",
|
||||||
|
"message": "window.requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "CallExpression > MemberExpression[object.name='window'][property.name='cancelAnimationFrame']",
|
||||||
|
"message": "window.cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "MemberExpression[object.name='window'][property.name='devicePixelRatio']",
|
||||||
|
"message": "window.devicePixelRatio should be avoided, get it from ICoreBrowserService."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"curly": [
|
||||||
|
"warn",
|
||||||
|
"multi-line"
|
||||||
|
],
|
||||||
|
"eqeqeq": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"no-duplicate-imports": "warn",
|
||||||
|
"no-else-return": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"allowElseIf": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-eval": "warn",
|
||||||
|
"no-irregular-whitespace": "warn",
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"patterns": [
|
||||||
|
".*\\/out\\/.*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-unsafe-finally": "warn",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-var": "warn",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-empty-pattern": "off",
|
||||||
|
"no-cond-assign": "off",
|
||||||
|
"no-case-declarations": "off",
|
||||||
|
"for-direction": "off",
|
||||||
|
"no-prototype-builtins": "off",
|
||||||
|
"no-useless-escape": "off",
|
||||||
|
"no-self-assign": "off",
|
||||||
|
"no-async-promise-executor": "off",
|
||||||
|
"prefer-rest-params": "off",
|
||||||
|
"no-control-regex": "off",
|
||||||
|
"no-fallthrough": "off",
|
||||||
|
"prefer-spread": "off",
|
||||||
|
"prefer-const": "warn",
|
||||||
|
"no-unused-expressions": "off",
|
||||||
|
"typescript/array-type": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"default": "array",
|
||||||
|
"readonly": "generic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/consistent-type-assertions": "warn",
|
||||||
|
"typescript/consistent-type-definitions": "warn",
|
||||||
|
"typescript/explicit-function-return-type": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"allowExpressions": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/explicit-member-accessibility": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"accessibility": "explicit",
|
||||||
|
"overrides": {
|
||||||
|
"constructors": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/no-confusing-void-expression": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"ignoreArrowShorthand": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/no-useless-constructor": "warn",
|
||||||
|
"typescript/prefer-nullish-coalescing": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"ignorePrimitives": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/prefer-namespace-keyword": "warn",
|
||||||
|
"typescript/no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"vars": "all",
|
||||||
|
"args": "none"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/no-require-imports": "off",
|
||||||
|
"typescript/no-explicit-any": "off",
|
||||||
|
"typescript/no-wrapper-object-types": "off",
|
||||||
|
"typescript/no-empty-object-type": "off",
|
||||||
|
"typescript/no-unsafe-function-type": "off",
|
||||||
|
"typescript/no-this-alias": "off",
|
||||||
|
"typescript/no-namespace": "off",
|
||||||
|
"typescript/no-duplicate-enum-values": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*.api.ts",
|
||||||
|
"**/*.test.ts"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"eslint-js/object-curly-spacing": "off",
|
||||||
|
"@stylistic/max-len": "off",
|
||||||
|
"typescript/no-unused-vars": "off",
|
||||||
|
"typescript/explicit-function-return-type": "off",
|
||||||
|
"typescript/explicit-member-accessibility": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"demo/**/*.ts",
|
||||||
|
"**/*.benchmark.ts"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"typescript/prefer-nullish-coalescing": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"options": {
|
||||||
|
"maxWarnings": 0
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"builtin": true
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"typings/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"jsPlugins": [
|
||||||
|
"@stylistic/eslint-plugin",
|
||||||
|
{
|
||||||
|
"name": "jsdoc-js",
|
||||||
|
"specifier": "eslint-plugin-jsdoc"
|
||||||
|
},
|
||||||
|
"oxlint-plugin-eslint"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@stylistic/indent": [
|
||||||
|
"warn",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"@stylistic/semi": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@stylistic/quotes": [
|
||||||
|
"warn",
|
||||||
|
"single",
|
||||||
|
{
|
||||||
|
"allowTemplateLiterals": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/member-delimiter-style": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"multiline": {
|
||||||
|
"delimiter": "semi",
|
||||||
|
"requireLast": true
|
||||||
|
},
|
||||||
|
"singleline": {
|
||||||
|
"delimiter": "comma",
|
||||||
|
"requireLast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/type-annotation-spacing": "warn",
|
||||||
|
"@stylistic/keyword-spacing": "warn",
|
||||||
|
"@stylistic/max-len": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"code": 1000,
|
||||||
|
"comments": 80,
|
||||||
|
"ignoreUrls": true,
|
||||||
|
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/no-extra-semi": "error",
|
||||||
|
"@stylistic/no-trailing-spaces": "warn",
|
||||||
|
"eslint-js/object-curly-spacing": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@stylistic/spaced-comment": [
|
||||||
|
"warn",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"markers": [
|
||||||
|
"/"
|
||||||
|
],
|
||||||
|
"exceptions": [
|
||||||
|
"-"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/comma-dangle": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"objects": "never",
|
||||||
|
"arrays": "never",
|
||||||
|
"functions": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@stylistic/eol-last": "warn",
|
||||||
|
"curly": [
|
||||||
|
"warn",
|
||||||
|
"multi-line"
|
||||||
|
],
|
||||||
|
"eqeqeq": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"jsdoc-js/check-alignment": "warn",
|
||||||
|
"jsdoc-js/check-param-names": "warn",
|
||||||
|
"no-irregular-whitespace": "warn",
|
||||||
|
"typescript/array-type": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"default": "array",
|
||||||
|
"readonly": "generic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/explicit-function-return-type": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"allowExpressions": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/prefer-namespace-keyword": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
-3
@@ -15,9 +15,6 @@
|
|||||||
"--recursive",
|
"--recursive",
|
||||||
"${workspaceRoot}/out/**/*.test.js"
|
"${workspaceRoot}/out/**/*.test.js"
|
||||||
],
|
],
|
||||||
"env": {
|
|
||||||
"NODE_PATH": "${workspaceRoot}/out"
|
|
||||||
},
|
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
|
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
|
||||||
"internalConsoleOptions": "openOnSessionStart"
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ npm run build && npm run esbuild # Build all TypeScript and bundle
|
|||||||
- Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit)
|
- Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit)
|
||||||
- Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work
|
- Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work
|
||||||
- Integration tests by addon: `npm run test-integration -- --suite=addon-search`. Suites always follow the format `addon-<something>`
|
- Integration tests by addon: `npm run test-integration -- --suite=addon-search`. Suites always follow the format `addon-<something>`
|
||||||
|
- Lint: `npm run lint` (oxlint with type-aware rules, then ESLint for `naming-convention` only), `npm run lint-api` for `typings/`, `npm run lint-fix` for oxlint auto-fix
|
||||||
- Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them
|
- Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them
|
||||||
|
|
||||||
## Addon Development Pattern
|
## Addon Development Pattern
|
||||||
@@ -98,3 +99,15 @@ const cell = line?.getCell(0);
|
|||||||
## Writing unit tests
|
## Writing unit tests
|
||||||
|
|
||||||
- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.
|
- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.
|
||||||
|
|
||||||
|
## Cursor Cloud specific instructions
|
||||||
|
|
||||||
|
**Demo server**: Start with `npm start` (port 3000). The demo server uses node-pty to spawn real shell sessions over WebSocket. Integration tests auto-start it via Playwright's `webServer` config, so you don't need to start it manually for `npm run test-integration`.
|
||||||
|
|
||||||
|
**Build before testing**: Always run `npm run build && npm run esbuild` before `npm run test-unit`. Integration tests also need `npm run esbuild-demo-client` and `npm run esbuild-demo-server`. The update script handles this automatically on session start.
|
||||||
|
|
||||||
|
**No external services**: This project has zero external dependencies (no databases, Docker, or APIs). Everything runs locally with Node.js.
|
||||||
|
|
||||||
|
**TypeScript compiler**: The project uses `tsgo` (native TypeScript compiler preview) rather than standard `tsc`. It's installed via the `@typescript/native-preview` package.
|
||||||
|
|
||||||
|
**Lint only changed files**: Prefer `npm run lint-changes` over `npm run lint` when iterating on code changes — it's significantly faster.
|
||||||
|
|||||||
+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;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## @xterm/addon-image
|
## @xterm/addon-image
|
||||||
|
|
||||||
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
|
Inline image output in xterm.js. Supports SIXEL, iTerm's inline image protocol (IIP)
|
||||||
|
and partially Kitty's terminal graphics prtocol (TGP).
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
@@ -43,9 +44,6 @@ terminal.loadAddon(imageAddon);
|
|||||||
|
|
||||||
### General Notes
|
### General Notes
|
||||||
|
|
||||||
- *IMPORTANT:* The worker approach as done in previous releases got removed.
|
|
||||||
The addon contructor no longer expects a worker path as first argument.
|
|
||||||
|
|
||||||
- By default the addon will activate these `windowOptions` reports on the terminal:
|
- By default the addon will activate these `windowOptions` reports on the terminal:
|
||||||
- getWinSizePixels (CSI 14 t)
|
- getWinSizePixels (CSI 14 t)
|
||||||
- getCellSizePixels (CSI 16 t)
|
- getCellSizePixels (CSI 16 t)
|
||||||
@@ -73,7 +71,7 @@ terminal.loadAddon(imageAddon);
|
|||||||
|
|
||||||
- **Cursor Positioning**
|
- **Cursor Positioning**
|
||||||
If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).
|
If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).
|
||||||
Other cursor positioning modes as used by xterm or mintty are not supported.
|
Other cursor positioning modes as used by xterm or mintty are not supported for sixel.
|
||||||
|
|
||||||
- **SIXEL Palette Handling**
|
- **SIXEL Palette Handling**
|
||||||
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
|
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
|
||||||
@@ -92,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.
|
||||||
|
|
||||||
@@ -100,17 +98,24 @@ terminal.loadAddon(imageAddon);
|
|||||||
Set by default, change it with `{iipSupport: true}`.
|
Set by default, change it with `{iipSupport: true}`.
|
||||||
|
|
||||||
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
|
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
|
||||||
- Supported formats: PNG, JPEG and GIF
|
- Supported formats: PNG, JPEG, GIF and QOI
|
||||||
- No animation support.
|
- No animation support.
|
||||||
- Image type hinting is not supported (always deducted from data header).
|
- Image type hinting is not supported (always deducted from data header).
|
||||||
- File download is not supported.
|
- File download is not supported.
|
||||||
- Filename gets parsed but not used.
|
- Filename gets parsed but not used.
|
||||||
- Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).
|
- Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).
|
||||||
- Payload size may not exceed CEIL(sizeParameter * 4 / 3).
|
|
||||||
- Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).
|
- Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).
|
||||||
- Image pixel size is restricted by `pixelLimit` (pre- and post resizing).
|
- Image pixel size is restricted by `pixelLimit` (pre- and post resizing).
|
||||||
- Size parameter is restricted by `iipSizeLimit`.
|
- Playload size is restricted by `iipSizeLimit`.
|
||||||
- Cursor positioning behaves the same as for sixel (see above).
|
- size parameter is not mandatory anymore.
|
||||||
|
- Cursor gets positioned at the next cell of the last image line.
|
||||||
|
- ReportCellSize sequence is supported.
|
||||||
|
- Hi-res display support is currently limited (image gets blurred).
|
||||||
|
|
||||||
|
- **Kitty Graphics Support (TGP)**
|
||||||
|
Set by default, change it with `{kittySupport: true}`.
|
||||||
|
|
||||||
|
Note that the kitty graphics support is still WIP.
|
||||||
|
|
||||||
|
|
||||||
### Storage and Drawing Settings
|
### Storage and Drawing Settings
|
||||||
@@ -127,6 +132,7 @@ The addon exposes two properties to interact with the storage limits at runtime:
|
|||||||
By default the addon will show a placeholder pattern for evicted images that are still part
|
By default the addon will show a placeholder pattern for evicted images that are still part
|
||||||
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
|
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
|
||||||
|
|
||||||
|
|
||||||
### Image Data Retrieval
|
### Image Data Retrieval
|
||||||
|
|
||||||
The addon provides the following API endpoints to retrieve raw image data as canvas:
|
The addon provides the following API endpoints to retrieve raw image data as canvas:
|
||||||
@@ -140,11 +146,12 @@ The addon provides the following API endpoints to retrieve raw image data as can
|
|||||||
The buffer position is the 0-based absolute index (including scrollback at top).
|
The buffer position is the 0-based absolute index (including scrollback at top).
|
||||||
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
|
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
|
||||||
|
|
||||||
|
|
||||||
### 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)
|
||||||
@@ -170,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`
|
- `sixelSizeLimit | iipSizeLimit | kittySizeLimit`
|
||||||
A constructor setting. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken SIXEL sequences at an earlier phase, thus may stop the decoder from entering its memory intensive task 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
|
||||||
@@ -211,23 +218,5 @@ _How can I adjust the memory usage?_
|
|||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
- Sixel support and image handling in xterm.js is considered beta quality.
|
- Sixel and IIP support and image handling in xterm.js is considered beta quality.
|
||||||
- IIP support is in alpha stage. Please file a bug for any awkwardities.
|
- Kitty support is in alpha stage. Please file a bug for any awkwardities or missing features.
|
||||||
|
|
||||||
|
|
||||||
### Changelog
|
|
||||||
|
|
||||||
- 0.5.0 integrate with xtermjs base repo (at v0.4.3)
|
|
||||||
- 0.4.3 defer canvas creation
|
|
||||||
- 0.4.2 fix image canvas resize
|
|
||||||
- 0.4.1 compat release for xterm.js 5.2.0
|
|
||||||
- 0.4.0 IIP support
|
|
||||||
- 0.3.1 compat release for xterm.js 5.1.0
|
|
||||||
- 0.3.0 important change: worker removed from addon
|
|
||||||
- 0.2.0 compat release for xterm.js 5.0.0
|
|
||||||
- 0.1.3 bugfix: avoid striping
|
|
||||||
- 0.1.2 bugfix: reset clear flag
|
|
||||||
- 0.1.1 bugfixes:
|
|
||||||
- clear sticky image tiles on render
|
|
||||||
- create folder from bootstrap.sh
|
|
||||||
- fix peer dependency in package.json
|
|
||||||
|
|||||||
Binary file not shown.
@@ -8,7 +8,7 @@ import { IIPImageStorage } from './IIPImageStorage';
|
|||||||
import { CELL_SIZE_DEFAULT } from './ImageStorage';
|
import { CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||||
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
||||||
import QoiDecoder from 'xterm-wasm-parts/lib/qoi/QoiDecoder.wasm';
|
import QoiDecoder from 'xterm-wasm-parts/lib/qoi/QoiDecoder.wasm';
|
||||||
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
|
import { HeaderParser, IHeaderFields, HeaderState, SequenceType } from './IIPHeaderParser';
|
||||||
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
||||||
|
|
||||||
// Local const enum mirror - esbuild can't inline const enums from external packages
|
// Local const enum mirror - esbuild can't inline const enums from external packages
|
||||||
@@ -23,6 +23,7 @@ const enum DecoderConst {
|
|||||||
|
|
||||||
// default IIP header values
|
// default IIP header values
|
||||||
const DEFAULT_HEADER: IHeaderFields = {
|
const DEFAULT_HEADER: IHeaderFields = {
|
||||||
|
type: SequenceType.INVALID,
|
||||||
name: 'Unnamed file',
|
name: 'Unnamed file',
|
||||||
size: 0,
|
size: 0,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
@@ -39,6 +40,8 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
private _dec: Base64Decoder;
|
private _dec: Base64Decoder;
|
||||||
private _qoiDec: QoiDecoder;
|
private _qoiDec: QoiDecoder;
|
||||||
private _metrics = UNSUPPORTED_TYPE;
|
private _metrics = UNSUPPORTED_TYPE;
|
||||||
|
private _isMultipart = false;
|
||||||
|
private _abortMulti = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _opts: IImageAddonOptions,
|
private readonly _opts: IImageAddonOptions,
|
||||||
@@ -52,11 +55,14 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
|
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {}
|
public reset(): void {
|
||||||
|
this._hp.reset();
|
||||||
|
this._dec.release();
|
||||||
|
this._qoiDec.release();
|
||||||
|
}
|
||||||
|
|
||||||
public start(): void {
|
public start(): void {
|
||||||
this._aborted = false;
|
this._aborted = false;
|
||||||
this._header = DEFAULT_HEADER;
|
|
||||||
this._metrics = UNSUPPORTED_TYPE;
|
this._metrics = UNSUPPORTED_TYPE;
|
||||||
this._hp.reset();
|
this._hp.reset();
|
||||||
}
|
}
|
||||||
@@ -76,15 +82,27 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dataPos > 0) {
|
if (dataPos > 0) {
|
||||||
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
const seqType = this._hp.fields.type;
|
||||||
if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
|
if (seqType === SequenceType.FILE) {
|
||||||
|
if (this._isMultipart) {
|
||||||
|
this._isMultipart = false;
|
||||||
|
this._abortMulti = false;
|
||||||
|
this._dec.release();
|
||||||
|
}
|
||||||
|
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
||||||
|
if (!this._header.inline) {
|
||||||
|
this._aborted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._dec.init();
|
||||||
|
} else if (this._abortMulti) {
|
||||||
this._aborted = true;
|
this._aborted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._dec.init();
|
|
||||||
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
|
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
|
||||||
this._dec.release();
|
this._dec.release();
|
||||||
this._aborted = true;
|
this._aborted = true;
|
||||||
|
if (this._isMultipart) this._abortMulti = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,22 +111,58 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
public end(success: boolean): boolean | Promise<boolean> {
|
public end(success: boolean): boolean | Promise<boolean> {
|
||||||
if (this._aborted) return true;
|
if (this._aborted) return true;
|
||||||
|
|
||||||
|
if (this._hp.state !== HeaderState.END) {
|
||||||
|
if (this._hp.end()) return true;
|
||||||
|
}
|
||||||
|
const seqType = this._hp.fields.type;
|
||||||
|
|
||||||
|
if (seqType === SequenceType.FILEPART) return true;
|
||||||
|
|
||||||
|
if (seqType === SequenceType.REPORTCELLSIZE) {
|
||||||
|
// OSC 1337 ; ReportCellSize=[height];[width];[scale] ST
|
||||||
|
let width = CELL_SIZE_DEFAULT.width;
|
||||||
|
let height = CELL_SIZE_DEFAULT.height;
|
||||||
|
if (this._renderer.dimensions) {
|
||||||
|
width = this._renderer.dimensions.css.canvas.width / this._coreTerminal.cols;
|
||||||
|
height = this._renderer.dimensions.css.canvas.height / this._coreTerminal.rows;
|
||||||
|
}
|
||||||
|
const scale = this._coreTerminal._core._coreBrowserService?.dpr ?? 1;
|
||||||
|
const report = `\x1b]1337;ReportCellSize=${height.toFixed(3)};${width.toFixed(3)};${scale.toFixed(3)}\x1b\\`;
|
||||||
|
this._coreTerminal.input(report, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seqType === SequenceType.MULTIPARTFILE) {
|
||||||
|
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
||||||
|
this._isMultipart = true;
|
||||||
|
this._abortMulti = false;
|
||||||
|
this._dec.release();
|
||||||
|
this._dec.init();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seqType === SequenceType.FILEEND) {
|
||||||
|
if (!this._isMultipart) return true;
|
||||||
|
this._isMultipart = false;
|
||||||
|
if (this._abortMulti || this._header.type !== SequenceType.MULTIPARTFILE) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallthrough for SequenceType.FILE & SequenceType.FILEEND
|
||||||
|
|
||||||
let w = 0;
|
let w = 0;
|
||||||
let h = 0;
|
let h = 0;
|
||||||
|
|
||||||
// early exit condition chain
|
// early exit condition chain
|
||||||
let cond: number | boolean = true;
|
let cond: number | boolean;
|
||||||
if (cond = success) {
|
if (cond = success) {
|
||||||
if (cond = !this._dec.end()) {
|
if (cond = !this._dec.end()) {
|
||||||
if (cond = this._dec.data8.length === this._header.size) {
|
this._metrics = imageType(this._dec.data8);
|
||||||
this._metrics = imageType(this._dec.data8);
|
if (cond = this._metrics.mime !== 'unsupported') {
|
||||||
if (cond = this._metrics.mime !== 'unsupported') {
|
w = this._metrics.width;
|
||||||
w = this._metrics.width;
|
h = this._metrics.height;
|
||||||
h = this._metrics.height;
|
if (cond = w && h && w * h < this._opts.pixelLimit) {
|
||||||
if (cond = w && h && w * h < this._opts.pixelLimit) {
|
[w, h] = this._resize(w, h).map(Math.floor);
|
||||||
[w, h] = this._resize(w, h).map(Math.floor);
|
cond = w && h && w * h < this._opts.pixelLimit;
|
||||||
cond = w && h && w * h < this._opts.pixelLimit;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';
|
import { HeaderParser, HeaderState, IHeaderFields, SequenceType } from './IIPHeaderParser';
|
||||||
|
|
||||||
|
|
||||||
const CASES: [string, IHeaderFields][] = [
|
const CASES: [string, IHeaderFields][] = [
|
||||||
['File=size=123456;name=dGVzdA==:', {name: 'test', size: 123456}],
|
['File=size=123456;name=dGVzdA==:', {type: SequenceType.FILE, name: 'test', size: 123456}],
|
||||||
['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],
|
['File=size=123456;name=dGVzdA:', {type: SequenceType.FILE, name: 'test', size: 123456}],
|
||||||
// utf-8 encoding in name
|
// utf-8 encoding in name
|
||||||
['File=size=123456;name=w7xtbMOkdXTDnw==:', {name: 'ümläutß', size: 123456}],
|
['File=size=123456;name=w7xtbMOkdXTDnw==:', {type: SequenceType.FILE, name: 'ümläutß', size: 123456}],
|
||||||
['File=size=123456;name=w7xtbMOkdXTDnw:', {name: 'ümläutß', size: 123456}],
|
['File=size=123456;name=w7xtbMOkdXTDnw:', {type: SequenceType.FILE, name: 'ümläutß', size: 123456}],
|
||||||
// full header spec
|
// full header spec
|
||||||
[
|
[
|
||||||
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
||||||
{
|
{
|
||||||
|
type: SequenceType.FILE,
|
||||||
inline: 1,
|
inline: 1,
|
||||||
width: '10px',
|
width: '10px',
|
||||||
height: '20%',
|
height: '20%',
|
||||||
@@ -28,6 +29,7 @@ const CASES: [string, IHeaderFields][] = [
|
|||||||
[
|
[
|
||||||
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
||||||
{
|
{
|
||||||
|
type: SequenceType.FILE,
|
||||||
inline: 1,
|
inline: 1,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: '20',
|
height: '20',
|
||||||
@@ -101,7 +103,7 @@ describe('IIPHeaderParser', () => {
|
|||||||
let res = hp.parse(inp, 0, inp.length);
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
assert.strictEqual(res, inp.length);
|
assert.strictEqual(res, inp.length);
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
assert.deepEqual(hp.fields, {name: 'test', size: 0});
|
assert.deepEqual(hp.fields, {type: SequenceType.FILE, name: 'test', size: 0});
|
||||||
hp.reset();
|
hp.reset();
|
||||||
inp = fromBs(CASES[0][0]);
|
inp = fromBs(CASES[0][0]);
|
||||||
res = hp.parse(inp, 0, inp.length);
|
res = hp.parse(inp, 0, inp.length);
|
||||||
@@ -115,7 +117,7 @@ describe('IIPHeaderParser', () => {
|
|||||||
let res = hp.parse(inp, 0, inp.length);
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
assert.strictEqual(res, inp.length);
|
assert.strictEqual(res, inp.length);
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
assert.deepEqual(hp.fields, {name: '', size: 123456});
|
assert.deepEqual(hp.fields, {type: SequenceType.FILE, name: '', size: 123456});
|
||||||
hp.reset();
|
hp.reset();
|
||||||
inp = fromBs(CASES[0][0]);
|
inp = fromBs(CASES[0][0]);
|
||||||
res = hp.parse(inp, 0, inp.length);
|
res = hp.parse(inp, 0, inp.length);
|
||||||
@@ -135,4 +137,124 @@ describe('IIPHeaderParser', () => {
|
|||||||
assert.strictEqual(hp.state, HeaderState.END);
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||||
});
|
});
|
||||||
|
it('FilePart sequence', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('FilePart=w7xtbMOkdXTDnw');
|
||||||
|
const res = hp.parse(inp, 0, inp.length);
|
||||||
|
assert.strictEqual(res, 9);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(hp.fields.type, SequenceType.FILEPART);
|
||||||
|
});
|
||||||
|
it('FilePart sequence - bytewise', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('FilePart=w7xtbMOkdXTDnw');
|
||||||
|
let res = -2;
|
||||||
|
let pos = 0;
|
||||||
|
while (res === -2 && pos < inp.length) {
|
||||||
|
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
||||||
|
}
|
||||||
|
assert.strictEqual(res, 1);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(hp.fields.type, SequenceType.FILEPART);
|
||||||
|
});
|
||||||
|
it('MultipartFile sequence', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('MultipartFile=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw');
|
||||||
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
|
assert.strictEqual(res, -2);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.VALUE);
|
||||||
|
res = hp.end();
|
||||||
|
assert.strictEqual(res, 0);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(
|
||||||
|
hp.fields,
|
||||||
|
{
|
||||||
|
type: SequenceType.MULTIPARTFILE,
|
||||||
|
inline: 1,
|
||||||
|
width: '10px',
|
||||||
|
height: '20%',
|
||||||
|
preserveAspectRatio: 1,
|
||||||
|
size: 123456,
|
||||||
|
name: 'ümläutß'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('MultipartFile sequence - bytewise', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('MultipartFile=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw');
|
||||||
|
let res = -2;
|
||||||
|
let pos = 0;
|
||||||
|
while (res === -2 && pos < inp.length) {
|
||||||
|
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
||||||
|
}
|
||||||
|
assert.strictEqual(res, -2);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.VALUE);
|
||||||
|
res = hp.end();
|
||||||
|
assert.strictEqual(res, 0);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(
|
||||||
|
hp.fields,
|
||||||
|
{
|
||||||
|
type: SequenceType.MULTIPARTFILE,
|
||||||
|
inline: 1,
|
||||||
|
width: '10px',
|
||||||
|
height: '20%',
|
||||||
|
preserveAspectRatio: 1,
|
||||||
|
size: 123456,
|
||||||
|
name: 'ümläutß'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('FileEnd sequence', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('FileEnd');
|
||||||
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
|
assert.strictEqual(res, -2);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.START);
|
||||||
|
res = hp.end();
|
||||||
|
assert.strictEqual(res, 0);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(hp.fields.type, SequenceType.FILEEND);
|
||||||
|
});
|
||||||
|
it('FileEnd sequence - bytewise', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('FileEnd');
|
||||||
|
let res = -2;
|
||||||
|
let pos = 0;
|
||||||
|
while (res === -2 && pos < inp.length) {
|
||||||
|
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
||||||
|
}
|
||||||
|
assert.strictEqual(res, -2);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.START);
|
||||||
|
res = hp.end();
|
||||||
|
assert.strictEqual(res, 0);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(hp.fields.type, SequenceType.FILEEND);
|
||||||
|
});
|
||||||
|
it('ReportCellSize sequence', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('ReportCellSize');
|
||||||
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
|
assert.strictEqual(res, -2);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.START);
|
||||||
|
res = hp.end();
|
||||||
|
assert.strictEqual(res, 0);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(hp.fields, { type: SequenceType.REPORTCELLSIZE });
|
||||||
|
});
|
||||||
|
it('ReportCellSize sequence - bytewise', () => {
|
||||||
|
const hp = new HeaderParser();
|
||||||
|
const inp = fromBs('ReportCellSize');
|
||||||
|
let res = -2;
|
||||||
|
let pos = 0;
|
||||||
|
while (res === -2 && pos < inp.length) {
|
||||||
|
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
||||||
|
}
|
||||||
|
assert.strictEqual(res, -2);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.START);
|
||||||
|
res = hp.end();
|
||||||
|
assert.strictEqual(res, 0);
|
||||||
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
|
assert.deepEqual(hp.fields, { type: SequenceType.REPORTCELLSIZE });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,9 +6,27 @@
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
declare const Buffer: any;
|
declare const Buffer: any;
|
||||||
|
|
||||||
|
export const enum HeaderState {
|
||||||
|
START = 0,
|
||||||
|
ABORT = 1,
|
||||||
|
KEY = 2,
|
||||||
|
VALUE = 3,
|
||||||
|
END = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum SequenceType {
|
||||||
|
INVALID = 0,
|
||||||
|
FILE = 1,
|
||||||
|
MULTIPARTFILE = 2,
|
||||||
|
FILEPART = 3,
|
||||||
|
FILEEND = 4,
|
||||||
|
REPORTCELLSIZE = 5
|
||||||
|
}
|
||||||
|
|
||||||
export interface IHeaderFields {
|
export interface IHeaderFields {
|
||||||
[key: string]: number | string | Uint32Array | null | undefined;
|
[key: string]: number | string | Uint32Array | null | undefined;
|
||||||
|
// sequence type
|
||||||
|
type: SequenceType;
|
||||||
// base-64 encoded filename. Defaults to "Unnamed file".
|
// base-64 encoded filename. Defaults to "Unnamed file".
|
||||||
name: string;
|
name: string;
|
||||||
// File size in bytes. The file transfer will be canceled if this size is exceeded.
|
// File size in bytes. The file transfer will be canceled if this size is exceeded.
|
||||||
@@ -29,14 +47,6 @@ export interface IHeaderFields {
|
|||||||
inline?: number;
|
inline?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum HeaderState {
|
|
||||||
START = 0,
|
|
||||||
ABORT = 1,
|
|
||||||
KEY = 2,
|
|
||||||
VALUE = 3,
|
|
||||||
END = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// field value decoders
|
// field value decoders
|
||||||
|
|
||||||
// ASCII bytes to string
|
// ASCII bytes to string
|
||||||
@@ -92,7 +102,19 @@ const DECODERS: {[key: string]: (v: Uint32Array) => number | string} = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// sequence type markers
|
||||||
|
// File
|
||||||
const FILE_MARKER = [70, 105, 108, 101];
|
const FILE_MARKER = [70, 105, 108, 101];
|
||||||
|
// MultipartFile
|
||||||
|
const MULTIPARTFILE_MARKER = [77, 117, 108, 116, 105, 112, 97, 114, 116, 70, 105, 108, 101];
|
||||||
|
// FilePart
|
||||||
|
const FILEPART_MARKER = [70, 105, 108, 101, 80, 97, 114, 116];
|
||||||
|
// FileEnd
|
||||||
|
const FILEEND_MARKER = [70, 105, 108, 101, 69, 110, 100];
|
||||||
|
// ReportCellSize
|
||||||
|
const REPORTCELLSIZE_MARKER = [82, 101, 112, 111, 114, 116, 67, 101, 108, 108, 83, 105, 122, 101];
|
||||||
|
|
||||||
|
// max allowed chars for sequence header
|
||||||
const MAX_FIELDCHARS = 1024;
|
const MAX_FIELDCHARS = 1024;
|
||||||
|
|
||||||
|
|
||||||
@@ -111,12 +133,43 @@ export class HeaderParser {
|
|||||||
this._key = '';
|
this._key = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public end(): number {
|
||||||
|
if (this.state === HeaderState.START) {
|
||||||
|
if (this._position === FILEEND_MARKER.length) {
|
||||||
|
for (let k = 0; k < FILEEND_MARKER.length; ++k) {
|
||||||
|
if (this._buffer[k] !== FILEEND_MARKER[k]) return this._a();
|
||||||
|
}
|
||||||
|
this.fields['type'] = SequenceType.FILEEND;
|
||||||
|
this.state = HeaderState.END;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (this._position === REPORTCELLSIZE_MARKER.length) {
|
||||||
|
for (let k = 0; k < REPORTCELLSIZE_MARKER.length; ++k) {
|
||||||
|
if (this._buffer[k] !== REPORTCELLSIZE_MARKER[k]) return this._a();
|
||||||
|
}
|
||||||
|
this.fields['type'] = SequenceType.REPORTCELLSIZE;
|
||||||
|
this.state = HeaderState.END;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this._a();
|
||||||
|
}
|
||||||
|
if (this.state === HeaderState.END) return 0;
|
||||||
|
if (this.state === HeaderState.VALUE
|
||||||
|
&& this.fields.type === SequenceType.MULTIPARTFILE
|
||||||
|
) {
|
||||||
|
if (!this._storeValue(this._position)) return this._a();
|
||||||
|
this.state = HeaderState.END;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this._a();
|
||||||
|
}
|
||||||
|
|
||||||
public parse(data: Uint32Array, start: number, end: number): number {
|
public parse(data: Uint32Array, start: number, end: number): number {
|
||||||
let state = this.state;
|
let state = this.state;
|
||||||
let pos = this._position;
|
let pos = this._position;
|
||||||
const buffer = this._buffer;
|
const buffer = this._buffer;
|
||||||
if (state === HeaderState.ABORT || state === HeaderState.END) return -1;
|
if (state === HeaderState.ABORT || state === HeaderState.END) return -1;
|
||||||
if (state === HeaderState.START && pos > 6) return -1;
|
if (state === HeaderState.START && pos > 14) return -1;
|
||||||
for (let i = start; i < end; ++i) {
|
for (let i = start; i < end; ++i) {
|
||||||
const c = data[i];
|
const c = data[i];
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@@ -127,8 +180,29 @@ export class HeaderParser {
|
|||||||
break;
|
break;
|
||||||
case 61: // =
|
case 61: // =
|
||||||
if (state === HeaderState.START) {
|
if (state === HeaderState.START) {
|
||||||
for (let k = 0; k < FILE_MARKER.length; ++k) {
|
if (buffer[0] === 70) {
|
||||||
if (buffer[k] !== FILE_MARKER[k]) return this._a();
|
// 'File' or 'FilePart'
|
||||||
|
let k = 0;
|
||||||
|
for (; k < FILE_MARKER.length; ++k) {
|
||||||
|
if (buffer[k] !== FILE_MARKER[k]) return this._a();
|
||||||
|
}
|
||||||
|
this.fields['type'] = SequenceType.FILE;
|
||||||
|
if (pos === FILEPART_MARKER.length) {
|
||||||
|
for (; k < FILEPART_MARKER.length; ++k) {
|
||||||
|
if (buffer[k] !== FILEPART_MARKER[k]) return this._a();
|
||||||
|
}
|
||||||
|
this.fields['type'] = SequenceType.FILEPART;
|
||||||
|
this.state = HeaderState.END;
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
} else if (buffer[0] === 77) {
|
||||||
|
// 'MultipartFile'
|
||||||
|
for (let k = 0; k < MULTIPARTFILE_MARKER.length; ++k) {
|
||||||
|
if (buffer[k] !== MULTIPARTFILE_MARKER[k]) return this._a();
|
||||||
|
}
|
||||||
|
this.fields['type'] = SequenceType.MULTIPARTFILE;
|
||||||
|
} else {
|
||||||
|
return this._a();
|
||||||
}
|
}
|
||||||
state = HeaderState.KEY;
|
state = HeaderState.KEY;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
@@ -158,6 +232,7 @@ export class HeaderParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _a(): number {
|
private _a(): number {
|
||||||
|
this.fields.type = SequenceType.INVALID;
|
||||||
this.state = HeaderState.ABORT;
|
this.state = HeaderState.ABORT;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IAddImageOpts } from './Types';
|
||||||
import { ImageStorage } from './ImageStorage';
|
import { ImageStorage } from './ImageStorage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,6 +13,7 @@ import { ImageStorage } from './ImageStorage';
|
|||||||
* - Always uses scrolling mode (cursor advances with image)
|
* - Always uses scrolling mode (cursor advances with image)
|
||||||
*/
|
*/
|
||||||
export class IIPImageStorage {
|
export class IIPImageStorage {
|
||||||
|
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _storage: ImageStorage
|
private readonly _storage: ImageStorage
|
||||||
) {}
|
) {}
|
||||||
@@ -21,6 +23,6 @@ export class IIPImageStorage {
|
|||||||
* Always uses scrolling mode — cursor advances past the image.
|
* Always uses scrolling mode — cursor advances past the image.
|
||||||
*/
|
*/
|
||||||
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
||||||
this._storage.addImage(img, true);
|
this._storage.addImage(img, this._addImageOpts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ const TEST_IMAGES: [string, IMetrics][] = [
|
|||||||
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
|
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
|
||||||
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
|
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
|
||||||
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
|
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
|
||||||
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]
|
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }],
|
||||||
|
['dice.qoi', { mime: 'image/qoi', width: 800, height: 600 }],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -137,11 +137,6 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
|||||||
|
|
||||||
// enable size reports
|
// enable size reports
|
||||||
if (this._opts.enableSizeReports) {
|
if (this._opts.enableSizeReports) {
|
||||||
// const windowOptions = terminal.getOption('windowOptions');
|
|
||||||
// windowOptions.getWinSizePixels = true;
|
|
||||||
// windowOptions.getCellSizePixels = true;
|
|
||||||
// windowOptions.getWinSizeChars = true;
|
|
||||||
// terminal.setOption('windowOptions', windowOptions);
|
|
||||||
const windowOps = terminal.options.windowOptions ?? {};
|
const windowOps = terminal.options.windowOptions ?? {};
|
||||||
windowOps.getWinSizePixels = true;
|
windowOps.getWinSizePixels = true;
|
||||||
windowOps.getCellSizePixels = true;
|
windowOps.getCellSizePixels = true;
|
||||||
@@ -260,7 +255,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _report(s: string): void {
|
private _report(s: string): void {
|
||||||
this._terminal?._core.coreService.triggerDataEvent(s);
|
this._terminal?._core.input(s, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _decset(params: (number | number[])[]): boolean {
|
private _decset(params: (number | number[])[]): boolean {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|||||||
public clearLines(start: number, end: number, layer?: ImageLayer): void {
|
public clearLines(start: number, end: number, layer?: ImageLayer): void {
|
||||||
const y = start * (this.dimensions?.css.cell.height || 0);
|
const y = start * (this.dimensions?.css.cell.height || 0);
|
||||||
const w = this.dimensions?.css.canvas.width || 0;
|
const w = this.dimensions?.css.canvas.width || 0;
|
||||||
const h = (++end - start) * (this.dimensions?.css.cell.height || 0);
|
const h = (end + 1 - start) * (this.dimensions?.css.cell.height || 0);
|
||||||
if (!layer || layer === 'top') {
|
if (!layer || layer === 'top') {
|
||||||
this._layers.get('top')?.clearRect(0, y, w, h);
|
this._layers.get('top')?.clearRect(0, y, w, h);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
|
|
||||||
import { IDisposable } from '@xterm/xterm';
|
import { IDisposable } from '@xterm/xterm';
|
||||||
import { ImageRenderer } from './ImageRenderer';
|
import { ImageRenderer } from './ImageRenderer';
|
||||||
import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';
|
import {
|
||||||
|
ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec,
|
||||||
|
IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes,
|
||||||
|
UnderlineStyle, IAddImageOpts
|
||||||
|
} from './Types';
|
||||||
|
|
||||||
|
|
||||||
// fallback default cell size
|
// fallback default cell size
|
||||||
@@ -235,13 +239,15 @@ export class ImageStorage implements IDisposable {
|
|||||||
/**
|
/**
|
||||||
* Method to add an image to the storage.
|
* Method to add an image to the storage.
|
||||||
* @param img - The image to add (canvas or bitmap).
|
* @param img - The image to add (canvas or bitmap).
|
||||||
* @param scrolling - When true, cursor advances with the image (lineFeed per row).
|
* @param opts - Options for addImage:
|
||||||
* When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).
|
* - scrolling: When true, cursor advances with the image.
|
||||||
* @param layer - Which canvas layer to render on ('top' or 'bottom').
|
* When false, image is placed at ORIGIN and cursor does not move.
|
||||||
* @param zIndex - Z-index for image layering within the same layer.
|
* - layer: Which canvas layer to render on ('top' or 'bottom').
|
||||||
|
* - zIndex: Z-index for image layering within the same layer.
|
||||||
|
* - cursorPos: 'vt340' for bottom-left, 'iip' for bottom.right.
|
||||||
* @returns The internal image ID assigned to the stored image.
|
* @returns The internal image ID assigned to the stored image.
|
||||||
*/
|
*/
|
||||||
public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer = 'top', zIndex: number = 0): number {
|
public addImage(img: HTMLCanvasElement | ImageBitmap, opts: IAddImageOpts): number {
|
||||||
// never allow storage to exceed memory limit
|
// never allow storage to exceed memory limit
|
||||||
this._evictOldest(img.width * img.height);
|
this._evictOldest(img.width * img.height);
|
||||||
|
|
||||||
@@ -263,7 +269,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
let offset = originX;
|
let offset = originX;
|
||||||
let tileCount = 0;
|
let tileCount = 0;
|
||||||
|
|
||||||
if (!scrolling) {
|
if (!opts.scrolling) {
|
||||||
buffer.x = 0;
|
buffer.x = 0;
|
||||||
buffer.y = 0;
|
buffer.y = 0;
|
||||||
offset = 0;
|
offset = 0;
|
||||||
@@ -277,7 +283,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
|
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
|
||||||
tileCount++;
|
tileCount++;
|
||||||
}
|
}
|
||||||
if (scrolling) {
|
if (opts.scrolling) {
|
||||||
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
|
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
|
||||||
} else {
|
} else {
|
||||||
if (++buffer.y >= termRows) break;
|
if (++buffer.y >= termRows) break;
|
||||||
@@ -287,8 +293,12 @@ export class ImageStorage implements IDisposable {
|
|||||||
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
|
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
|
||||||
|
|
||||||
// cursor positioning modes
|
// cursor positioning modes
|
||||||
if (scrolling) {
|
if (opts.scrolling) {
|
||||||
buffer.x = offset;
|
if (opts.cursorPos === 'iip') {
|
||||||
|
buffer.x = Math.min(offset + cols, termCols);
|
||||||
|
} else {
|
||||||
|
buffer.x = offset;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
buffer.x = originX;
|
buffer.x = originX;
|
||||||
buffer.y = originY;
|
buffer.y = originY;
|
||||||
@@ -331,8 +341,8 @@ export class ImageStorage implements IDisposable {
|
|||||||
marker: endMarker || undefined,
|
marker: endMarker || undefined,
|
||||||
tileCount,
|
tileCount,
|
||||||
bufferType: this._terminal.buffer.active.type,
|
bufferType: this._terminal.buffer.active.type,
|
||||||
layer,
|
layer: opts.layer,
|
||||||
zIndex
|
zIndex: opts.zIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
// finally add the image
|
// finally add the image
|
||||||
@@ -426,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;
|
||||||
}
|
}
|
||||||
@@ -451,11 +461,10 @@ 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;
|
||||||
}
|
}
|
||||||
e = nextE;
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
col--;
|
col--;
|
||||||
@@ -506,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;
|
||||||
@@ -551,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) {
|
||||||
@@ -571,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) {
|
||||||
@@ -602,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
|
||||||
@@ -618,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 {
|
||||||
@@ -643,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) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||||
import { IImageAddonOptions, ITerminalExt } from './Types';
|
import { IImageAddonOptions, ITerminalExt, IAddImageOpts } from './Types';
|
||||||
import { ImageRenderer } from './ImageRenderer';
|
import { ImageRenderer } from './ImageRenderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,6 +15,7 @@ import { ImageRenderer } from './ImageRenderer';
|
|||||||
* - advanceCursor for empty sixels carrying only height
|
* - advanceCursor for empty sixels carrying only height
|
||||||
*/
|
*/
|
||||||
export class SixelImageStorage {
|
export class SixelImageStorage {
|
||||||
|
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'vt340' };
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _storage: ImageStorage,
|
private readonly _storage: ImageStorage,
|
||||||
private readonly _opts: IImageAddonOptions,
|
private readonly _opts: IImageAddonOptions,
|
||||||
@@ -27,7 +28,8 @@ export class SixelImageStorage {
|
|||||||
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
|
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
|
||||||
*/
|
*/
|
||||||
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
||||||
this._storage.addImage(img, this._opts.sixelScrolling);
|
this._addImageOpts.scrolling = this._opts.sixelScrolling;
|
||||||
|
this._storage.addImage(img, this._addImageOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +101,14 @@ export interface ICellSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ImageLayer = 'top' | 'bottom';
|
export type ImageLayer = 'top' | 'bottom';
|
||||||
|
export type CursorPos = 'vt340' | 'iip';
|
||||||
|
|
||||||
|
export interface IAddImageOpts {
|
||||||
|
scrolling: boolean;
|
||||||
|
layer: ImageLayer;
|
||||||
|
zIndex: number;
|
||||||
|
cursorPos: CursorPos;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IImageSpec {
|
export interface IImageSpec {
|
||||||
orig: HTMLCanvasElement | ImageBitmap | undefined;
|
orig: HTMLCanvasElement | ImageBitmap | undefined;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { IDisposable } from '@xterm/xterm';
|
import { IDisposable } from '@xterm/xterm';
|
||||||
import { ImageStorage } from '../ImageStorage';
|
import { ImageStorage } from '../ImageStorage';
|
||||||
import { ImageLayer } from '../Types';
|
import { ImageLayer, IAddImageOpts } from '../Types';
|
||||||
import { IKittyImageData } from './KittyGraphicsTypes';
|
import { IKittyImageData } from './KittyGraphicsTypes';
|
||||||
|
|
||||||
// Kitty-specific image storage controller.
|
// Kitty-specific image storage controller.
|
||||||
@@ -42,6 +42,7 @@ export class KittyImageStorage implements IDisposable {
|
|||||||
this._images.delete(kittyId);
|
this._images.delete(kittyId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _storage: ImageStorage
|
private readonly _storage: ImageStorage
|
||||||
@@ -98,7 +99,10 @@ export class KittyImageStorage implements IDisposable {
|
|||||||
if (oldStorageId !== undefined) {
|
if (oldStorageId !== undefined) {
|
||||||
this._storageIdToKittyId.delete(oldStorageId);
|
this._storageIdToKittyId.delete(oldStorageId);
|
||||||
}
|
}
|
||||||
const storageId = this._storage.addImage(image, scrolling, layer, zIndex);
|
this._addImageOpts.scrolling = scrolling;
|
||||||
|
this._addImageOpts.layer = layer;
|
||||||
|
this._addImageOpts.zIndex = zIndex;
|
||||||
|
const storageId = this._storage.addImage(image, this._addImageOpts);
|
||||||
this._kittyIdToStorageId.set(kittyId, storageId);
|
this._kittyIdToStorageId.set(kittyId, storageId);
|
||||||
this._storageIdToKittyId.set(storageId, kittyId);
|
this._storageIdToKittyId.set(storageId, kittyId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
@@ -290,6 +293,67 @@ test.describe('ImageAddon', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('IIP support', () => {
|
||||||
|
test('size params is optional/informative', async () => {
|
||||||
|
// too small size param
|
||||||
|
await ctx.proxy.write(`\x1b]1337;File=inline=1;size=5:${PALETTE_PNG_BASE64}\x07`);
|
||||||
|
deepStrictEqual(await getOrigSize(1), [640, 80]);
|
||||||
|
// no size aparm at all
|
||||||
|
await ctx.proxy.write(`\x1b[10H\x1b]1337;File=inline=1:${PALETTE_QOI_BASE64}\x07`);
|
||||||
|
deepStrictEqual(await getOrigSize(2), [640, 80]);
|
||||||
|
});
|
||||||
|
test.skip('FilePart support - bytewise', async () => {
|
||||||
|
// opener
|
||||||
|
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
||||||
|
// byte-wise
|
||||||
|
for (let i = 0; i < PALETTE_PNG_BASE64.length; ++i) {
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64[i]}\x07`);
|
||||||
|
}
|
||||||
|
// finalizer
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
||||||
|
deepStrictEqual(await getOrigSize(1), [640, 80]);
|
||||||
|
});
|
||||||
|
test('FilePart support - chunks', async () => {
|
||||||
|
// opener
|
||||||
|
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
||||||
|
// chunks
|
||||||
|
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
|
||||||
|
}
|
||||||
|
// finalizer
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
||||||
|
deepStrictEqual(await getOrigSize(1), [640, 80]);
|
||||||
|
});
|
||||||
|
test('FilePart support - edgecases', async () => {
|
||||||
|
// multiple opener do not harm
|
||||||
|
for (let i = 0; i < 10; ++i) {
|
||||||
|
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
||||||
|
}
|
||||||
|
// multiple finalizer do not harm
|
||||||
|
for (let i = 0; i < 5; ++i) {
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
||||||
|
}
|
||||||
|
// chunks w'o opener are ignored
|
||||||
|
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
|
||||||
|
}
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
||||||
|
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0);
|
||||||
|
});
|
||||||
|
test('FilePart support - other IIP sequence cancels chunked image', async () => {
|
||||||
|
// opener
|
||||||
|
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
||||||
|
// chunks
|
||||||
|
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
|
||||||
|
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
|
||||||
|
}
|
||||||
|
// finalizer skipped
|
||||||
|
// write spinfox.png
|
||||||
|
await ctx.proxy.write(TESTDATA_IIP[1][0]);
|
||||||
|
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('IIP support - testimages', () => {
|
test.describe('IIP support - testimages', () => {
|
||||||
test('palette.png', async () => {
|
test('palette.png', async () => {
|
||||||
await ctx.proxy.write(TESTDATA_IIP[0][0]);
|
await ctx.proxy.write(TESTDATA_IIP[0][0]);
|
||||||
|
|||||||
@@ -539,7 +539,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
|
|
||||||
test.describe('Query support (a=q)', () => {
|
test.describe('Query support (a=q)', () => {
|
||||||
test('responds with OK for capability query without payload', async () => {
|
test('responds with OK for capability query without payload', async () => {
|
||||||
let response = '';
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -548,12 +547,11 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
response = await ctx.page.evaluate('window.kittyResponse');
|
const response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=31;OK\x1b\\');
|
strictEqual(response, '\x1b_Gi=31;OK\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('responds with OK for valid PNG query', async () => {
|
test('responds with OK for valid PNG query', async () => {
|
||||||
let response = '';
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -562,7 +560,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write(`\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
await ctx.proxy.write(`\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
response = await ctx.page.evaluate('window.kittyResponse');
|
const response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=42;OK\x1b\\');
|
strictEqual(response, '\x1b_Gi=42;OK\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -578,7 +576,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('responds with error for invalid base64', async () => {
|
test('responds with error for invalid base64', async () => {
|
||||||
let response = '';
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -587,12 +584,11 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
response = await ctx.page.evaluate('window.kittyResponse');
|
const response: string = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response.startsWith('\x1b_Gi=60;EINVAL:'), true);
|
strictEqual(response.startsWith('\x1b_Gi=60;EINVAL:'), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('responds with error for RGB data without dimensions', async () => {
|
test('responds with error for RGB data without dimensions', async () => {
|
||||||
let response = '';
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -601,7 +597,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
response = await ctx.page.evaluate('window.kittyResponse');
|
const response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=70;EINVAL:width and height required for raw pixel data\x1b\\');
|
strictEqual(response, '\x1b_Gi=70;EINVAL:width and height required for raw pixel data\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -642,7 +638,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('responds with EINVAL when both i and I keys are specified', async () => {
|
test('responds with EINVAL when both i and I keys are specified', async () => {
|
||||||
let response = '';
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -652,12 +647,11 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
response = await ctx.page.evaluate('window.kittyResponse');
|
const response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=100;EINVAL:cannot specify both i and I keys\x1b\\');
|
strictEqual(response, '\x1b_Gi=100;EINVAL:cannot specify both i and I keys\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('responds with EINVAL for i+I conflict even without payload', async () => {
|
test('responds with EINVAL for i+I conflict even without payload', async () => {
|
||||||
let response = '';
|
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -667,7 +661,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
response = await ctx.page.evaluate('window.kittyResponse');
|
const response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
|
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,15 +31,11 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^6.0.0",
|
"lru-cache": "^11.3.6",
|
||||||
"opentype.js": "^0.8.0"
|
"opentype.js": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lru-cache": "^5.1.0",
|
"@types/opentype.js": "^1.3.9",
|
||||||
"@types/opentype.js": "^0.7.0",
|
"font-finder": "^1.1.0"
|
||||||
"axios": "^1.15.2",
|
|
||||||
"font-finder": "^1.1.0",
|
|
||||||
"mkdirp": "0.5.5",
|
|
||||||
"yauzl": "^3.2.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as opentype from 'opentype.js';
|
import * as opentype from 'opentype.js';
|
||||||
import LRUCache = require('lru-cache');
|
import { LRUCache } from 'lru-cache';
|
||||||
|
|
||||||
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
|
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
|
||||||
import mergeTrees from './merge';
|
import mergeTrees from './merge';
|
||||||
@@ -23,8 +23,8 @@ class FontImpl implements IFont {
|
|||||||
|
|
||||||
if (options.cacheSize > 0) {
|
if (options.cacheSize > 0) {
|
||||||
this._cache = new LRUCache({
|
this._cache = new LRUCache({
|
||||||
max: options.cacheSize,
|
maxSize: options.cacheSize,
|
||||||
length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
|
sizeCalculation: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
|
|||||||
// current range
|
// current range
|
||||||
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
|
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
|
||||||
ranges.splice(i, 1);
|
ranges.splice(i, 1);
|
||||||
inRange = false;
|
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
// Case 7: New range extends from previous range past the
|
// Case 7: New range extends from previous range past the
|
||||||
|
|||||||
@@ -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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ export class SearchEngine {
|
|||||||
this._terminal.clearSelection();
|
this._terminal.clearSelection();
|
||||||
|
|
||||||
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;
|
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;
|
||||||
let startCol = this._terminal.cols;
|
const startCol = this._terminal.cols;
|
||||||
const isReverseSearch = true;
|
const isReverseSearch = true;
|
||||||
|
|
||||||
this._lineCache.initLinesCache();
|
this._lineCache.initLinesCache();
|
||||||
@@ -186,14 +186,14 @@ export class SearchEngine {
|
|||||||
let result: ISearchResult | undefined;
|
let result: ISearchResult | undefined;
|
||||||
if (prevSelectedPos) {
|
if (prevSelectedPos) {
|
||||||
searchPosition.startRow = startRow = prevSelectedPos.start.y;
|
searchPosition.startRow = startRow = prevSelectedPos.start.y;
|
||||||
searchPosition.startCol = startCol = prevSelectedPos.start.x;
|
searchPosition.startCol = prevSelectedPos.start.x;
|
||||||
if (cachedSearchTerm !== term) {
|
if (cachedSearchTerm !== term) {
|
||||||
// Try to expand selection to right first.
|
// Try to expand selection to right first.
|
||||||
result = this._findInLine(term, searchPosition, searchOptions, false);
|
result = this._findInLine(term, searchPosition, searchOptions, false);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
// If selection was not able to be expanded to the right, then try reverse search
|
// If selection was not able to be expanded to the right, then try reverse search
|
||||||
searchPosition.startRow = startRow = prevSelectedPos.end.y;
|
searchPosition.startRow = startRow = prevSelectedPos.end.y;
|
||||||
searchPosition.startCol = startCol = prevSelectedPos.end.x;
|
searchPosition.startCol = prevSelectedPos.end.x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,10 +7,10 @@
|
|||||||
"rootDir": "..",
|
"rootDir": "..",
|
||||||
"outDir": "../out-benchmark",
|
"outDir": "../out-benchmark",
|
||||||
"types": ["../../../node_modules/@types/node"],
|
"types": ["../../../node_modules/@types/node"],
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "nodenext",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"target": "es2021",
|
"target": "es2021",
|
||||||
"module": "commonjs",
|
"module": "nodenext",
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": ["../../../src/common/*"],
|
"common/*": ["../../../src/common/*"],
|
||||||
"browser/*": ["../../../src/browser/*"],
|
"browser/*": ["../../../src/browser/*"],
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
"package": "../../node_modules/.bin/webpack",
|
"package": "../../node_modules/.bin/webpack",
|
||||||
"prepublishOnly": "npm run package",
|
"prepublishOnly": "npm run package",
|
||||||
"start": "node ../../demo/start",
|
"start": "node ../../demo/start",
|
||||||
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
|
"benchmark": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
|
||||||
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
|
"benchmark-baseline": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
|
||||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
|
"benchmark-eval": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"rootDir": "..",
|
"rootDir": "..",
|
||||||
"outDir": "../out-benchmark",
|
"outDir": "../out-benchmark",
|
||||||
"types": ["../../../node_modules/@types/node"],
|
"types": ["../../../node_modules/@types/node"],
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "nodenext",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"module": "commonjs",
|
"module": "nodenext",
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": ["../../../src/common/*"],
|
"common/*": ["../../../src/common/*"],
|
||||||
"browser/*": ["../../../src/browser/*"],
|
"browser/*": ["../../../src/browser/*"],
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
"package": "../../node_modules/.bin/webpack",
|
"package": "../../node_modules/.bin/webpack",
|
||||||
"prepublishOnly": "npm run package",
|
"prepublishOnly": "npm run package",
|
||||||
"start": "node ../../demo/start",
|
"start": "node ../../demo/start",
|
||||||
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json out-benchmark/benchmark/*benchmark.js",
|
"benchmark": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json out-benchmark/benchmark/*benchmark.js",
|
||||||
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
|
"benchmark-baseline": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
|
||||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
|
"benchmark-eval": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -78,7 +78,22 @@ test.describe('WebFontsAddon', () => {
|
|||||||
const data = await ctx.page.evaluate(`
|
const data = await ctx.page.evaluate(`
|
||||||
document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/fonts/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
|
document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/fonts/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
|
||||||
`);
|
`);
|
||||||
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
|
let fonts = await getDocumentFonts();
|
||||||
|
// safari may have loaded the font, firefox & chrome never load it
|
||||||
|
if (browser.browserType().name() === 'webkit') {
|
||||||
|
try {
|
||||||
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
|
||||||
|
} catch (e) {
|
||||||
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// broken case: webfont in ctor without addon usage
|
// broken case: webfont in ctor without addon usage
|
||||||
await ctx.page.evaluate(`
|
await ctx.page.evaluate(`
|
||||||
@@ -86,15 +101,20 @@ test.describe('WebFontsAddon', () => {
|
|||||||
window.helperTerm.open(term.element);
|
window.helperTerm.open(term.element);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// safari loads the font, firefox & chrome dont
|
fonts = await getDocumentFonts();
|
||||||
|
// safari might load the font, firefox & chrome dont
|
||||||
if (browser.browserType().name() === 'webkit') {
|
if (browser.browserType().name() === 'webkit') {
|
||||||
try {
|
try {
|
||||||
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loading' }]);
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
|
try {
|
||||||
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
|
||||||
|
} catch (e) {
|
||||||
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
|
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// good case: addon fixes layout for webfont in ctor
|
// good case: addon fixes layout for webfont in ctor
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
"lib": [
|
"lib": [
|
||||||
"es2021",
|
"es2021",
|
||||||
],
|
],
|
||||||
// "downlevelIteration": true,
|
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../out-test",
|
"outDir": "../out-test",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
@@ -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('');
|
||||||
@@ -113,8 +114,8 @@ export class LinkComputer {
|
|||||||
let line: IBufferLine | undefined;
|
let line: IBufferLine | undefined;
|
||||||
let topIdx = lineIndex;
|
let topIdx = lineIndex;
|
||||||
let bottomIdx = lineIndex;
|
let bottomIdx = lineIndex;
|
||||||
let length = 0;
|
let length;
|
||||||
let content = '';
|
let content;
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
||||||
if ((line = terminal.buffer.active.getLine(lineIndex))) {
|
if ((line = terminal.buffer.active.getLine(lineIndex))) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
"lib": [
|
"lib": [
|
||||||
"es2021",
|
"es2021",
|
||||||
],
|
],
|
||||||
// "downlevelIteration": true,
|
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../out-test",
|
"outDir": "../out-test",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -427,8 +427,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
let line: IBufferLine;
|
let line: IBufferLine;
|
||||||
let joinedRanges: [number, number][];
|
let joinedRanges: [number, number][];
|
||||||
let isJoined: boolean;
|
let isJoined: boolean;
|
||||||
let skipJoinedCheckUntilX: number = 0;
|
let skipJoinedCheckUntilX: number;
|
||||||
let isValidJoinRange: boolean = true;
|
let isValidJoinRange: boolean;
|
||||||
let lastCharX: number;
|
let lastCharX: number;
|
||||||
let range: [number, number];
|
let range: [number, number];
|
||||||
let isCursorRow: boolean;
|
let isCursorRow: boolean;
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition } from './Types';
|
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition } from './Types';
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
/* oxlint-disable @stylistic/max-len -- generated glyph path data */
|
||||||
|
|
||||||
const enum Shapes {
|
const enum Shapes {
|
||||||
/** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
|
/** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"types": [
|
"types": [
|
||||||
"../../../node_modules/@types/mocha"
|
"../../../node_modules/@types/mocha"
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+41
-11
@@ -33,17 +33,47 @@ if (files.length === 0) {
|
|||||||
|
|
||||||
console.log(`Linting ${files.length} changed file(s)...`);
|
console.log(`Linting ${files.length} changed file(s)...`);
|
||||||
|
|
||||||
const eslintArgs = ['--max-warnings', '0', '--no-warn-ignored'];
|
const cwd = path.join(__dirname, '..');
|
||||||
if (fix) {
|
const isWin = process.platform === 'win32';
|
||||||
eslintArgs.push('--fix');
|
|
||||||
|
/**
|
||||||
|
* @param {string} command
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
function run(command, args) {
|
||||||
|
return new Promise(/** @type {(resolve: () => void, reject: (reason: Error) => void) => void} */ ((resolve, reject) => {
|
||||||
|
const child = spawn(command, args, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd,
|
||||||
|
shell: isWin
|
||||||
|
});
|
||||||
|
child.on('close', code => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`${command} exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
eslintArgs.push(...files);
|
|
||||||
|
|
||||||
const eslint = process.platform === 'win32' ? 'eslint.cmd' : 'eslint';
|
async function main() {
|
||||||
const child = spawn(eslint, eslintArgs, {
|
const oxlintArgs = ['--type-aware', '--max-warnings', '0'];
|
||||||
stdio: 'inherit',
|
if (fix) {
|
||||||
cwd: path.join(__dirname, '..'),
|
oxlintArgs.push('--fix');
|
||||||
shell: process.platform === 'win32'
|
}
|
||||||
|
oxlintArgs.push(...files);
|
||||||
|
|
||||||
|
const oxlint = isWin ? 'oxlint.cmd' : 'oxlint';
|
||||||
|
await run(oxlint, oxlintArgs);
|
||||||
|
|
||||||
|
const eslintArgs = ['--config', 'eslint.config.naming.mjs', '--max-warnings', '0', ...files];
|
||||||
|
const eslint = isWin ? 'eslint.cmd' : 'eslint';
|
||||||
|
await run(eslint, eslintArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error(err.message);
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('close', code => process.exit(code ?? 0));
|
|
||||||
|
|||||||
+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);
|
||||||
|
|||||||
+3
-5
@@ -8,7 +8,7 @@ const path = require('path');
|
|||||||
|
|
||||||
const COVERAGE_LINES_THRESHOLD = 40;
|
const COVERAGE_LINES_THRESHOLD = 40;
|
||||||
|
|
||||||
// Add `out` to the NODE_PATH so absolute paths can be resolved.
|
// Add `out-esbuild` to NODE_PATH so addon path-mapped imports (common/*, browser/*) resolve.
|
||||||
const env = { ...process.env };
|
const env = { ...process.env };
|
||||||
env.NODE_PATH = path.resolve(__dirname, '../out-esbuild');
|
env.NODE_PATH = path.resolve(__dirname, '../out-esbuild');
|
||||||
|
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
const dtIip = document.createElement('dt');
|
const dtIip = document.createElement('dt');
|
||||||
dtIip.textContent = 'IIP (iTerm)';
|
dtIip.textContent = 'IIP (iTerm)';
|
||||||
dlIip.appendChild(dtIip);
|
dlIip.appendChild(dtIip);
|
||||||
this._addDdWithButton(dlIip, 'image-demo3', 'palette');
|
this._addDdWithButton(dlIip, 'image-demo3', 'palette (png File)');
|
||||||
|
this._addDdWithButton(dlIip, 'image-demo4', 'dice (qoi MultipartFile)');
|
||||||
container.appendChild(dlIip);
|
container.appendChild(dlIip);
|
||||||
|
|
||||||
// Kitty demos
|
// Kitty demos
|
||||||
@@ -141,6 +142,21 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const iipDemoMulti = (url: string) => () => fetch(url)
|
||||||
|
.then(resp => resp.arrayBuffer())
|
||||||
|
.then(buffer => {
|
||||||
|
const data = new Uint8Array(buffer);
|
||||||
|
let sdata = '';
|
||||||
|
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
|
||||||
|
const encoded = btoa(sdata);
|
||||||
|
this._terminal.write('\r\n');
|
||||||
|
this._terminal.write(`\x1b]1337;MultipartFile=inline=1\x1b\\`);
|
||||||
|
for (let i = 0; i < encoded.length; i += 100) {
|
||||||
|
this._terminal.write(`\x1b]1337;FilePart=${encoded.slice(i, i + 100)}\x1b\\`);
|
||||||
|
}
|
||||||
|
this._terminal.write(`\x1b]1337;FileEnd\x1b\\`);
|
||||||
|
});
|
||||||
|
|
||||||
const kittyDemo = (url: string) => () => fetch(url)
|
const kittyDemo = (url: string) => () => fetch(url)
|
||||||
.then(resp => resp.arrayBuffer())
|
.then(resp => resp.arrayBuffer())
|
||||||
.then(buffer => {
|
.then(buffer => {
|
||||||
@@ -158,6 +174,8 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||||
document.getElementById('image-demo3')!.addEventListener('click',
|
document.getElementById('image-demo3')!.addEventListener('click',
|
||||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||||
|
document.getElementById('image-demo4')!.addEventListener('click',
|
||||||
|
iipDemoMulti('https://raw.githubusercontent.com/jerch/xterm-wasm-parts/master/fixtures/qoi/dice.qoi'));
|
||||||
document.getElementById('image-demo-kitty1')!.addEventListener('click',
|
document.getElementById('image-demo-kitty1')!.addEventListener('click',
|
||||||
kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||||
|
|
||||||
@@ -176,7 +194,7 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
const pos = (this._terminal as any)._core._mouseCoordsService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
||||||
const x = pos[0] - 1;
|
const x = pos[0] - 1;
|
||||||
const y = pos[1] - 1;
|
const y = pos[1] - 1;
|
||||||
const canvas = ev.shiftKey
|
const canvas = ev.shiftKey
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class GpuWindow extends BaseWindow implements IControlWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _styleAtlasPage(canvas: HTMLCanvasElement): void {
|
private _styleAtlasPage(canvas: HTMLCanvasElement): void {
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// oxlint-disable-next-line eslint-js/no-restricted-syntax
|
||||||
const dpr = window.devicePixelRatio;
|
const dpr = window.devicePixelRatio;
|
||||||
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`;
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -43,9 +68,38 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _styleAtlasPage(canvas: HTMLCanvasElement): void {
|
private _styleAtlasPage(canvas: HTMLCanvasElement): void {
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// oxlint-disable-next-line eslint-js/no-restricted-syntax
|
||||||
const dpr = window.devicePixelRatio;
|
const dpr = window.devicePixelRatio;
|
||||||
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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-13
@@ -2,25 +2,54 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es2021",
|
"target": "es2021",
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
"outDir": "../out-demo/client",
|
"outDir": "../out-demo/client",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@xterm/addon-attach": ["../../addons/addon-attach"],
|
"@xterm/addon-attach": [
|
||||||
"@xterm/addon-clipboard": ["../../addons/addon-clipboard"],
|
"../../addons/addon-attach"
|
||||||
"@xterm/addon-fit": ["../../addons/addon-fit"],
|
],
|
||||||
"@xterm/addon-image": ["../../addons/addon-image"],
|
"@xterm/addon-clipboard": [
|
||||||
"@xterm/addon-progress": ["../../addons/addon-progress"],
|
"../../addons/addon-clipboard"
|
||||||
"@xterm/addon-search": ["../../addons/addon-search"],
|
],
|
||||||
"@xterm/addon-serialize": ["../../addons/addon-serialize"],
|
"@xterm/addon-fit": [
|
||||||
"@xterm/addon-web-fonts": ["../../addons/addon-web-fonts"],
|
"../../addons/addon-fit"
|
||||||
"@xterm/addon-web-links": ["../../addons/addon-web-links"],
|
],
|
||||||
"@xterm/addon-webgl": ["../../addons/addon-webgl"],
|
"@xterm/addon-image": [
|
||||||
"@xterm/addon-unicode11": ["../../addons/addon-unicode11"],
|
"../../addons/addon-image"
|
||||||
"@xterm/addon-unicode-graphemes": ["../../addons/addon-unicode-graphemes"],
|
],
|
||||||
"@xterm/addon-ligatures": ["../../addons/addon-ligatures"],
|
"@xterm/addon-progress": [
|
||||||
|
"../../addons/addon-progress"
|
||||||
|
],
|
||||||
|
"@xterm/addon-search": [
|
||||||
|
"../../addons/addon-search"
|
||||||
|
],
|
||||||
|
"@xterm/addon-serialize": [
|
||||||
|
"../../addons/addon-serialize"
|
||||||
|
],
|
||||||
|
"@xterm/addon-web-fonts": [
|
||||||
|
"../../addons/addon-web-fonts"
|
||||||
|
],
|
||||||
|
"@xterm/addon-web-links": [
|
||||||
|
"../../addons/addon-web-links"
|
||||||
|
],
|
||||||
|
"@xterm/addon-webgl": [
|
||||||
|
"../../addons/addon-webgl"
|
||||||
|
],
|
||||||
|
"@xterm/addon-unicode11": [
|
||||||
|
"../../addons/addon-unicode11"
|
||||||
|
],
|
||||||
|
"@xterm/addon-unicode-graphemes": [
|
||||||
|
"../../addons/addon-unicode-graphemes"
|
||||||
|
],
|
||||||
|
"@xterm/addon-ligatures": [
|
||||||
|
"../../addons/addon-ligatures"
|
||||||
|
],
|
||||||
"*": ["./*"]
|
"*": ["./*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function startServer(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
appWs.ws('/terminals/:pid', function (ws, req) {
|
appWs.ws('/terminals/:pid', function (ws, req) {
|
||||||
const term = terminals[parseInt(req.params.pid)];
|
const term = terminals[parseInt(Array.isArray(req.params.pid) ? req.params.pid[0] : req.params.pid)];
|
||||||
console.log('Connected to terminal ' + term.pid);
|
console.log('Connected to terminal ' + term.pid);
|
||||||
temporaryDisposable[term.pid].dispose();
|
temporaryDisposable[term.pid].dispose();
|
||||||
delete temporaryDisposable[term.pid];
|
delete temporaryDisposable[term.pid];
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ export default tseslint.config(
|
|||||||
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
|
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
|
||||||
'@stylistic/indent': ['warn', 2],
|
'@stylistic/indent': ['warn', 2],
|
||||||
'@stylistic/semi': ['warn', 'always'],
|
'@stylistic/semi': ['warn', 'always'],
|
||||||
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
|
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
|
||||||
'@stylistic/member-delimiter-style': ['warn', {
|
'@stylistic/member-delimiter-style': ['warn', {
|
||||||
multiline: { delimiter: 'semi', requireLast: true },
|
multiline: { delimiter: 'semi', requireLast: true },
|
||||||
singleline: { delimiter: 'comma', requireLast: false }
|
singleline: { delimiter: 'comma', requireLast: false }
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// @ts-check
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
const ignores = [
|
||||||
|
'addons/*/src/third-party/*.ts',
|
||||||
|
'**/out/*',
|
||||||
|
'**/out-test/*',
|
||||||
|
'**/out-esbuild/*',
|
||||||
|
'**/out-esbuild-test/*',
|
||||||
|
'**/inwasm-sdks/*',
|
||||||
|
'**/node_modules',
|
||||||
|
'**/*.js',
|
||||||
|
'**/*.mjs'
|
||||||
|
];
|
||||||
|
|
||||||
|
const namingConventionMain = [
|
||||||
|
'warn',
|
||||||
|
{ selector: 'default', format: ['camelCase'], filter: { regex: '^[a-z]', match: true } },
|
||||||
|
{ selector: 'variable', format: ['camelCase', 'UPPER_CASE'] },
|
||||||
|
{ selector: 'variable', filter: '^I.+Service$', format: ['PascalCase'], prefix: ['I'] },
|
||||||
|
{ selector: 'memberLike', modifiers: ['private'], format: ['camelCase'], leadingUnderscore: 'require' },
|
||||||
|
{ selector: 'memberLike', modifiers: ['protected'], format: ['camelCase'], leadingUnderscore: 'require' },
|
||||||
|
{ selector: 'enumMember', format: ['UPPER_CASE'] },
|
||||||
|
{ selector: 'property', modifiers: ['public'], format: ['camelCase', 'UPPER_CASE'], filter: { regex: '^[a-z]', match: true } },
|
||||||
|
{ selector: 'method', modifiers: ['public'], format: ['camelCase', 'UPPER_CASE'], custom: { regex: '^on[A-Z].+', match: false } },
|
||||||
|
{ selector: 'method', modifiers: ['private'], format: ['camelCase'], leadingUnderscore: 'require', custom: { regex: '^on[A-Z].+', match: false } },
|
||||||
|
{ selector: 'method', modifiers: ['protected'], format: ['camelCase'], leadingUnderscore: 'require', custom: { regex: '^on[A-Z].+', match: false } },
|
||||||
|
{ selector: 'typeLike', format: ['PascalCase'] },
|
||||||
|
{ selector: 'interface', format: ['PascalCase'], prefix: ['I'] }
|
||||||
|
];
|
||||||
|
|
||||||
|
const namingConventionTypings = [
|
||||||
|
'warn',
|
||||||
|
{ selector: 'typeLike', format: ['PascalCase'] },
|
||||||
|
{ selector: 'interface', format: ['PascalCase'], prefix: ['I'] }
|
||||||
|
];
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
ignores: ['typings/**/*.d.ts', 'addons/**/typings/**'],
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': tseslint.plugin
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/naming-convention': namingConventionMain
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['typings/**/*.d.ts'],
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': tseslint.plugin
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/naming-convention': namingConventionTypings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -17,7 +17,7 @@ export default tseslint.config(
|
|||||||
rules: {
|
rules: {
|
||||||
'@stylistic/indent': ['warn', 2],
|
'@stylistic/indent': ['warn', 2],
|
||||||
'@stylistic/semi': ['warn', 'always'],
|
'@stylistic/semi': ['warn', 'always'],
|
||||||
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
|
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
|
||||||
'@stylistic/member-delimiter-style': ['warn', {
|
'@stylistic/member-delimiter-style': ['warn', {
|
||||||
multiline: { delimiter: 'semi', requireLast: true },
|
multiline: { delimiter: 'semi', requireLast: true },
|
||||||
singleline: { delimiter: 'comma', requireLast: false }
|
singleline: { delimiter: 'comma', requireLast: false }
|
||||||
|
|||||||
Generated
+3115
-2321
File diff suppressed because it is too large
Load Diff
+35
-27
@@ -11,6 +11,9 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"addons/*"
|
"addons/*"
|
||||||
],
|
],
|
||||||
|
"overrides": {
|
||||||
|
"serialize-javascript": "^7.0.3"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
"command-line",
|
"command-line",
|
||||||
@@ -48,11 +51,11 @@
|
|||||||
"esbuild-demo-server-watch": "node bin/esbuild.mjs --demo-server --watch",
|
"esbuild-demo-server-watch": "node bin/esbuild.mjs --demo-server --watch",
|
||||||
"test": "npm run test-unit",
|
"test": "npm run test-unit",
|
||||||
"posttest": "npm run lint",
|
"posttest": "npm run lint",
|
||||||
"lint": "eslint --max-warnings 0 src/ addons/ demo/ test/",
|
"lint": "oxlint --type-aware --max-warnings 0 src addons demo test && eslint --config eslint.config.naming.mjs --max-warnings 0 src addons demo test",
|
||||||
"lint-changes": "node ./bin/lint_changes.js",
|
"lint-changes": "node ./bin/lint_changes.js",
|
||||||
"lint-changes-fix": "node ./bin/lint_changes.js --fix",
|
"lint-changes-fix": "node ./bin/lint_changes.js --fix",
|
||||||
"lint-fix": "eslint --fix src/ addons/ demo/ test/",
|
"lint-fix": "oxlint --type-aware --fix --max-warnings 0 src addons demo test",
|
||||||
"lint-api": "eslint --config eslint.config.typings.mjs --max-warnings 0 typings/",
|
"lint-api": "oxlint --max-warnings 0 -c .oxlintrc.typings.json typings && eslint --config eslint.config.naming.mjs --max-warnings 0 typings",
|
||||||
"test-unit": "node ./bin/test_unit.js",
|
"test-unit": "node ./bin/test_unit.js",
|
||||||
"test-unit-slow-tests": "npm run test-unit | grep \"ms)\"",
|
"test-unit-slow-tests": "npm run test-unit | grep \"ms)\"",
|
||||||
"test-unit-coverage": "node ./bin/test_unit.js --coverage",
|
"test-unit-coverage": "node ./bin/test_unit.js --coverage",
|
||||||
@@ -75,40 +78,45 @@
|
|||||||
"prepublishOnly": "npm run package"
|
"prepublishOnly": "npm run package"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
"@lunapaint/png-codec": "^0.2.0",
|
"@lunapaint/png-codec": "^0.2.0",
|
||||||
"@playwright/test": "^1.57.0",
|
"@playwright/test": "^1.59.1",
|
||||||
"@stylistic/eslint-plugin": "^4.4.1",
|
"@stylistic/eslint-plugin": "^5.10.0",
|
||||||
"@types/chai": "^4.2.22",
|
"@types/chai": "^5.2.3",
|
||||||
"@types/express": "4",
|
"@types/express": "^5.0.6",
|
||||||
"@types/express-ws": "^3.0.1",
|
"@types/express-ws": "^3.0.1",
|
||||||
"@types/jsdom": "^27.0.0",
|
"@types/jsdom": "^28.0.1",
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^10.0.10",
|
||||||
"@types/node": "^22.19.3",
|
"@types/node": "^22.19.3",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"@types/ws": "^8.2.0",
|
"@types/ws": "^8.2.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
||||||
"@typescript-eslint/parser": "^8.50.1",
|
"@typescript-eslint/parser": "^8.59.2",
|
||||||
"@typescript/native-preview": "^7.0.0-dev.20260213.1",
|
"@typescript/native-preview": "^7.0.0-dev.20260509.2",
|
||||||
"chai": "^4.3.4",
|
"chai": "^6.2.2",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"esbuild": "~0.25.2",
|
"esbuild": "^0.28.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^10.3.0",
|
||||||
"eslint-plugin-jsdoc": "^50.8.0",
|
"eslint-plugin-jsdoc": "^62.9.0",
|
||||||
"express": "^4.19.2",
|
"eslint-plugin-oxlint": "^1.67.0",
|
||||||
|
"express": "^5.2.1",
|
||||||
"express-ws": "^5.0.2",
|
"express-ws": "^5.0.2",
|
||||||
"jsdom": "^27.3.0",
|
"jsdom": "^29.1.1",
|
||||||
"mocha": "^10.1.0",
|
"mocha": "^11.7.5",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"node-pty": "^1.2.0-beta.9",
|
"node-pty": "^1.2.0-beta.9",
|
||||||
"nyc": "^17.1.0",
|
"nyc": "^18.0.0",
|
||||||
"source-map-loader": "^3.0.0",
|
"oxlint": "^1.67.0",
|
||||||
|
"oxlint-plugin-eslint": "^1.67.0",
|
||||||
|
"oxlint-tsgolint": "^0.23.0",
|
||||||
|
"source-map-loader": "^5.0.0",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"ts-loader": "^9.3.1",
|
"ts-loader": "^9.5.7",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.50.1",
|
"typescript-eslint": "^8.59.2",
|
||||||
"webpack": "^5.61.0",
|
"webpack": "^5.106.2",
|
||||||
"webpack-cli": "^4.9.1",
|
"webpack-cli": "^7.0.2",
|
||||||
"ws": "^8.2.3",
|
"ws": "^8.20.0",
|
||||||
"xterm-benchmark": "^0.3.1"
|
"xterm-benchmark": "^0.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Strings from 'browser/LocalizableStrings';
|
import * as Strings from './LocalizableStrings';
|
||||||
import { ITerminal, IRenderDebouncer } from 'browser/Types';
|
import { ITerminal, IRenderDebouncer } from './Types';
|
||||||
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
|
import { TimeBasedDebouncer } from './TimeBasedDebouncer';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from '../common/Lifecycle';
|
||||||
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
import { ICoreBrowserService, IRenderService } from './services/Services';
|
||||||
import { IBuffer } from 'common/buffer/Types';
|
import { IBuffer } from '../common/buffer/Types';
|
||||||
import { IInstantiationService } from 'common/services/Services';
|
import { IInstantiationService } from '../common/services/Services';
|
||||||
import { addDisposableListener } from 'browser/Dom';
|
import { addDisposableListener } from './Dom';
|
||||||
|
|
||||||
const enum Constants {
|
const enum Constants {
|
||||||
MAX_ROWS_TO_READ = 20
|
MAX_ROWS_TO_READ = 20
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import * as Clipboard from 'browser/Clipboard';
|
import * as Clipboard from './Clipboard';
|
||||||
|
|
||||||
describe('evaluatePastedTextProcessing', () => {
|
describe('evaluatePastedTextProcessing', () => {
|
||||||
it('should replace carriage return and/or line feed with carriage return', () => {
|
it('should replace carriage return and/or line feed with carriage return', () => {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ISelectionService } from 'browser/services/Services';
|
import { ISelectionService } from './services/Services';
|
||||||
import { ICoreService, IOptionsService } from 'common/services/Services';
|
import { ICoreService, IOptionsService } from '../common/services/Services';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares text to be pasted into the terminal by normalizing the line endings
|
* Prepares text to be pasted into the terminal by normalizing the line endings
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
import { ColorContrastCache } from './ColorContrastCache';
|
||||||
|
|
||||||
describe('ColorContrastCache', () => {
|
describe('ColorContrastCache', () => {
|
||||||
let cache: ColorContrastCache;
|
let cache: ColorContrastCache;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IColorContrastCache } from 'browser/Types';
|
import { IColorContrastCache } from './Types';
|
||||||
import { IColor } from 'common/Types';
|
import { IColor } from '../common/Types';
|
||||||
import { TwoKeyMap } from 'common/MultiKeyMap';
|
import { TwoKeyMap } from '../common/MultiKeyMap';
|
||||||
|
|
||||||
export class ColorContrastCache implements IColorContrastCache {
|
export class ColorContrastCache implements IColorContrastCache {
|
||||||
private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();
|
private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();
|
||||||
|
|||||||
@@ -22,43 +22,43 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
|
import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
|
||||||
import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';
|
import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from './Clipboard';
|
||||||
import * as Strings from 'browser/LocalizableStrings';
|
import * as Strings from './LocalizableStrings';
|
||||||
import { OscLinkProvider } from 'browser/OscLinkProvider';
|
import { OscLinkProvider } from './OscLinkProvider';
|
||||||
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types';
|
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from './Types';
|
||||||
import { Viewport } from 'browser/Viewport';
|
import { Viewport } from './Viewport';
|
||||||
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
|
import { BufferDecorationRenderer } from './decorations/BufferDecorationRenderer';
|
||||||
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
|
import { OverviewRulerRenderer } from './decorations/OverviewRulerRenderer';
|
||||||
import { CompositionHelper } from 'browser/input/CompositionHelper';
|
import { CompositionHelper } from './input/CompositionHelper';
|
||||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
import { DomRenderer } from './renderer/dom/DomRenderer';
|
||||||
import { IRenderer } from 'browser/renderer/shared/Types';
|
import { IRenderer } from './renderer/shared/Types';
|
||||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
import { CharSizeService } from './services/CharSizeService';
|
||||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
import { CharacterJoinerService } from './services/CharacterJoinerService';
|
||||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
import { CoreBrowserService } from './services/CoreBrowserService';
|
||||||
import { LinkProviderService } from 'browser/services/LinkProviderService';
|
import { LinkProviderService } from './services/LinkProviderService';
|
||||||
import { MouseCoordsService } from 'browser/services/MouseCoordsService';
|
import { MouseCoordsService } from './services/MouseCoordsService';
|
||||||
import { MouseService } from 'browser/services/MouseService';
|
import { MouseEventCssClasses, MouseService } from './services/MouseService';
|
||||||
import { RenderService } from 'browser/services/RenderService';
|
import { RenderService } from './services/RenderService';
|
||||||
import { SelectionService } from 'browser/services/SelectionService';
|
import { SelectionService } from './services/SelectionService';
|
||||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
|
||||||
import { ThemeService } from 'browser/services/ThemeService';
|
import { ThemeService } from './services/ThemeService';
|
||||||
import { KeyboardService } from 'browser/services/KeyboardService';
|
import { KeyboardService } from './services/KeyboardService';
|
||||||
import { channels, color, rgb } from 'common/Color';
|
import { channels, color, rgb } from '../common/Color';
|
||||||
import { CoreTerminal } from 'common/CoreTerminal';
|
import { CoreTerminal } from '../common/CoreTerminal';
|
||||||
import * as Browser from 'common/Platform';
|
import * as Browser from '../common/Platform';
|
||||||
import { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from 'common/Types';
|
import { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from '../common/Types';
|
||||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
|
||||||
import { IBuffer } from 'common/buffer/Types';
|
import { IBuffer } from '../common/buffer/Types';
|
||||||
import { C0, C1ESCAPED } from 'common/data/EscapeSequences';
|
import { C0, C1ESCAPED } from '../common/data/EscapeSequences';
|
||||||
import { toRgbString } from 'common/input/XParseColor';
|
import { toRgbString } from '../common/input/XParseColor';
|
||||||
import { DecorationService } from 'common/services/DecorationService';
|
import { DecorationService } from '../common/services/DecorationService';
|
||||||
import { IDecorationService } from 'common/services/Services';
|
import { IDecorationService } from '../common/services/Services';
|
||||||
import { WindowsOptionsReportType } from '../common/InputHandler';
|
import { WindowsOptionsReportType } from '../common/InputHandler';
|
||||||
import { AccessibilityManager } from './AccessibilityManager';
|
import { AccessibilityManager } from './AccessibilityManager';
|
||||||
import { Linkifier } from './Linkifier';
|
import { Linkifier } from './Linkifier';
|
||||||
import { Emitter, EventUtils, type IEvent } from 'common/Event';
|
import { Emitter, EventUtils, type IEvent } from '../common/Event';
|
||||||
import { addDisposableListener } from 'browser/Dom';
|
import { addDisposableListener } from './Dom';
|
||||||
import { MutableDisposable, toDisposable } from 'common/Lifecycle';
|
import { MutableDisposable, toDisposable } from '../common/Lifecycle';
|
||||||
|
|
||||||
export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||||
public textarea: HTMLTextAreaElement | undefined;
|
public textarea: HTMLTextAreaElement | undefined;
|
||||||
@@ -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;
|
||||||
@@ -212,7 +212,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|||||||
if (!this._themeService) return;
|
if (!this._themeService) return;
|
||||||
for (const req of event) {
|
for (const req of event) {
|
||||||
let acc: 'foreground' | 'background' | 'cursor' | 'ansi';
|
let acc: 'foreground' | 'background' | 'cursor' | 'ansi';
|
||||||
let ident = '';
|
let ident: string;
|
||||||
switch (req.index) {
|
switch (req.index) {
|
||||||
case SpecialColorIndex.FOREGROUND: // OSC 10 | 110
|
case SpecialColorIndex.FOREGROUND: // OSC 10 | 110
|
||||||
acc = 'foreground';
|
acc = 'foreground';
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -618,11 +618,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
|||||||
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
|
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
|
||||||
|
|
||||||
// apply mouse event classes set by escape codes before terminal was attached
|
// apply mouse event classes set by escape codes before terminal was attached
|
||||||
if (this.mouseStateService.areMouseEventsActive) {
|
if (this.mouseStateService.areMouseEventsActive && !this.options.mouseEventsRequireAlt) {
|
||||||
this._selectionService.disable();
|
this._selectionService.disable();
|
||||||
this.element.classList.add('enable-mouse-events');
|
this.element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
|
||||||
} else {
|
} else {
|
||||||
this._selectionService.enable();
|
this._selectionService.enable();
|
||||||
|
this.element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.screenReaderMode) {
|
if (this.options.screenReaderMode) {
|
||||||
@@ -1071,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;
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
* Minimal DOM helpers for xterm.js browser code.
|
* Minimal DOM helpers for xterm.js browser code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IntervalTimer } from 'common/Async';
|
import { IntervalTimer } from '../common/Async';
|
||||||
import { IDisposable } from 'common/Lifecycle';
|
import { IDisposable } from '../common/Lifecycle';
|
||||||
|
|
||||||
export function getWindow(e: Node | UIEvent | undefined | null): Window {
|
export function getWindow(e: Node | UIEvent | undefined | null): Window {
|
||||||
const candidateNode = e as Node | undefined | null;
|
const candidateNode = e as Node | undefined | null;
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { IBufferService } from 'common/services/Services';
|
import { IBufferService } from '../common/services/Services';
|
||||||
import { Linkifier } from './Linkifier';
|
import { Linkifier } from './Linkifier';
|
||||||
import { MockBufferService } from 'common/TestUtils.test';
|
import { MockBufferService } from '../common/TestUtils.test';
|
||||||
import { ILink } from 'browser/Types';
|
import { ILink } from './Types';
|
||||||
import { LinkProviderService } from 'browser/services/LinkProviderService';
|
import { LinkProviderService } from './services/LinkProviderService';
|
||||||
import jsdom = require('jsdom');
|
import jsdom = require('jsdom');
|
||||||
|
|
||||||
class TestLinkifier2 extends Linkifier {
|
class TestLinkifier2 extends Linkifier {
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from 'browser/Types';
|
import { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from './Types';
|
||||||
import { Disposable, dispose, toDisposable } from 'common/Lifecycle';
|
import { Disposable, dispose, toDisposable } from '../common/Lifecycle';
|
||||||
import { IDisposable } from 'common/Types';
|
import { IDisposable } from '../common/Types';
|
||||||
import { IBufferService } from 'common/services/Services';
|
import { IBufferService } from '../common/services/Services';
|
||||||
import { ILinkProviderService, IMouseCoordsService, IRenderService } from './services/Services';
|
import { ILinkProviderService, IMouseCoordsService, IRenderService } from './services/Services';
|
||||||
import { Emitter } from 'common/Event';
|
import { Emitter } from '../common/Event';
|
||||||
import { addDisposableListener } from 'browser/Dom';
|
import { addDisposableListener } from './Dom';
|
||||||
|
|
||||||
export class Linkifier extends Disposable implements ILinkifier2 {
|
export class Linkifier extends Disposable implements ILinkifier2 {
|
||||||
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
|
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { OscLinkProvider } from 'browser/OscLinkProvider';
|
import { OscLinkProvider } from './OscLinkProvider';
|
||||||
import { ILink } from 'browser/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;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IBufferRange, ILink } from 'browser/Types';
|
import { IBufferRange, ILink } from './Types';
|
||||||
import { ILinkProvider } from 'browser/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 {
|
||||||
private readonly _workCell = new CellData();
|
private readonly _workCell = new CellData();
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IRenderDebouncerWithCallback } from 'browser/Types';
|
import { IRenderDebouncerWithCallback } from './Types';
|
||||||
import { ICoreBrowserService } from 'browser/services/Services';
|
import { ICoreBrowserService } from './services/Services';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounces calls to render terminal rows using animation frames.
|
* Debounces calls to render terminal rows using animation frames.
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MockCompositionHelper, MockRenderer, MockViewport, TestTerminal } from 'browser/TestUtils.test';
|
import { MockCompositionHelper, MockRenderer, MockViewport, TestTerminal } from './TestUtils.test';
|
||||||
import type { IBrowser } from 'browser/Types';
|
import type { IBrowser } from './Types';
|
||||||
import { assert } from 'chai';
|
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,14 +7,14 @@ import * as path from 'path';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as pty from 'node-pty';
|
import * as pty from 'node-pty';
|
||||||
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
|
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
|
||||||
import { IDisposable } from '@xterm/xterm';
|
import { IDisposable } from '@xterm/xterm';
|
||||||
|
|
||||||
// all test files expect terminal in 80x25
|
// all test files expect terminal in 80x25
|
||||||
const COLS = 80;
|
const COLS = 80;
|
||||||
const ROWS = 25;
|
const ROWS = 25;
|
||||||
|
|
||||||
const escapeSequenceFilesDir = path.join(__dirname, '../../fixtures/escape_sequence_files');
|
const escapeSequenceFilesDir = path.join(__dirname, '../../test/fixtures/escape_sequence_files');
|
||||||
const TESTFILES = fs.readdirSync(escapeSequenceFilesDir)
|
const TESTFILES = fs.readdirSync(escapeSequenceFilesDir)
|
||||||
.filter(f => f.endsWith('.in'))
|
.filter(f => f.endsWith('.in'))
|
||||||
.map(f => path.join(escapeSequenceFilesDir, f));
|
.map(f => path.join(escapeSequenceFilesDir, f));
|
||||||
@@ -125,7 +125,7 @@ function formatError(input: string, output: string, expected: string): string {
|
|||||||
// simple debug output of terminal cells
|
// simple debug output of terminal cells
|
||||||
function terminalToString(term: CoreBrowserTerminal): string {
|
function terminalToString(term: CoreBrowserTerminal): string {
|
||||||
let result = '';
|
let result = '';
|
||||||
let lineText = '';
|
let lineText: string;
|
||||||
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
|
||||||
lineText = term.buffer.lines.get(line)!.translateToString(true);
|
lineText = term.buffer.lines.get(line)!.translateToString(true);
|
||||||
// rtrim empty cells as xterm does
|
// rtrim empty cells as xterm does
|
||||||
|
|||||||
@@ -4,21 +4,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
|
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
|
||||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
|
||||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from './renderer/shared/Types';
|
||||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from 'browser/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 { Buffer } from 'common/buffer/Buffer';
|
import { XtermListener, ICharset, ITerminalOptions, ColorIndex } from '../common/Types';
|
||||||
import * as Browser from 'common/Platform';
|
import { Buffer } from '../common/buffer/Buffer';
|
||||||
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
|
import * as Browser from '../common/Platform';
|
||||||
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from 'common/services/Services';
|
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
|
||||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from '../common/services/Services';
|
||||||
import { AttributeData } from 'common/buffer/AttributeData';
|
import { IFunctionIdentifier, IParams } from '../common/parser/Types';
|
||||||
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
import { AttributeData } from '../common/buffer/AttributeData';
|
||||||
import { css } from 'common/Color';
|
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from './selection/Types';
|
||||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
import { css } from '../common/Color';
|
||||||
import { Emitter, type IEvent } from 'common/Event';
|
import { createRenderDimensions } from './renderer/shared/RendererUtils';
|
||||||
|
import { Emitter, type IEvent } from '../common/Event';
|
||||||
|
|
||||||
export class TestTerminal extends CoreBrowserTerminal {
|
export class TestTerminal extends CoreBrowserTerminal {
|
||||||
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
|
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
|
||||||
@@ -258,15 +259,6 @@ export class MockBuffer implements IBuffer {
|
|||||||
public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
|
public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
|
||||||
return Buffer.prototype.getBlankLine.apply(this, arguments as any);
|
return Buffer.prototype.getBlankLine.apply(this, arguments as any);
|
||||||
}
|
}
|
||||||
public attachWrappedRow(y: number, fillCellData: ICellData): IBufferLine {
|
|
||||||
return Buffer.prototype.attachWrappedRow.apply(this, arguments as any);
|
|
||||||
}
|
|
||||||
public clearWrappedRow(y: number, fillCellData: ICellData, preserveContent?: boolean): void {
|
|
||||||
Buffer.prototype.clearWrappedRow.apply(this, arguments as any);
|
|
||||||
}
|
|
||||||
public createScrollWrappedLine(lineAbove: IBufferLine | undefined, attr: IAttributeData): IBufferLine {
|
|
||||||
return Buffer.prototype.createScrollWrappedLine.apply(this, arguments as any);
|
|
||||||
}
|
|
||||||
public getNullCell(attr?: IAttributeData): ICellData {
|
public getNullCell(attr?: IAttributeData): ICellData {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
|
import { IRenderDebouncer } from './Types';
|
||||||
|
|
||||||
import { IRenderDebouncer } from 'browser/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,11 +3,12 @@
|
|||||||
* @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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A portion of the public API that are implemented identially internally and simply passed through.
|
* A portion of the public API that are implemented identially internally and simply passed through.
|
||||||
|
|||||||
+10
-10
@@ -3,16 +3,16 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
import { ICoreBrowserService, IRenderService, IThemeService } from './services/Services';
|
||||||
import { ViewportConstants } from 'browser/shared/Constants';
|
import { ViewportConstants } from './shared/Constants';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from '../common/Lifecycle';
|
||||||
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from 'common/services/Services';
|
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from '../common/services/Services';
|
||||||
import { CoreMouseEventType } from 'common/Types';
|
import { CoreMouseEventType } from '../common/Types';
|
||||||
import { scheduleAtNextAnimationFrame } from 'browser/Dom';
|
import { scheduleAtNextAnimationFrame } from './Dom';
|
||||||
import { SmoothScrollableElement } from 'browser/scrollable/scrollableElement';
|
import { SmoothScrollableElement } from './scrollable/scrollableElement';
|
||||||
import type { IScrollableElementChangeOptions } from 'browser/scrollable/scrollableElementOptions';
|
import type { IScrollableElementChangeOptions } from './scrollable/scrollableElementOptions';
|
||||||
import { Emitter, EventUtils } from 'common/Event';
|
import { Emitter, EventUtils } from '../common/Event';
|
||||||
import { Scrollable, ScrollbarVisibility, type IScrollEvent } from 'browser/scrollable/scrollable';
|
import { Scrollable, ScrollbarVisibility, type IScrollEvent } from './scrollable/scrollable';
|
||||||
|
|
||||||
export class Viewport extends Disposable {
|
export class Viewport extends Disposable {
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
import { ICoreBrowserService, IRenderService } from '../services/Services';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from '../../common/Lifecycle';
|
||||||
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';
|
import { IBufferService, IDecorationService, IInternalDecoration } from '../../common/services/Services';
|
||||||
|
|
||||||
export class BufferDecorationRenderer extends Disposable {
|
export class BufferDecorationRenderer extends Disposable {
|
||||||
private readonly _container: HTMLElement;
|
private readonly _container: HTMLElement;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { ColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
import { ColorZoneStore } from './ColorZoneStore';
|
||||||
|
|
||||||
const optionsRedFull = {
|
const optionsRedFull = {
|
||||||
overviewRulerOptions: {
|
overviewRulerOptions: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IInternalDecoration } from 'common/services/Services';
|
import { IInternalDecoration } from '../../common/services/Services';
|
||||||
|
|
||||||
export interface IColorZoneStore {
|
export interface IColorZoneStore {
|
||||||
readonly zones: IColorZone[];
|
readonly zones: IColorZone[];
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
import { ColorZoneStore, IColorZone, IColorZoneStore } from './ColorZoneStore';
|
||||||
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
import { ICoreBrowserService, IRenderService, IThemeService } from '../services/Services';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from '../../common/Lifecycle';
|
||||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
import { IBufferService, IDecorationService, IOptionsService } from '../../common/services/Services';
|
||||||
|
|
||||||
const enum Constants {
|
const enum Constants {
|
||||||
OVERVIEW_RULER_BORDER_WIDTH = 1
|
OVERVIEW_RULER_BORDER_WIDTH = 1
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { CompositionHelper } from 'browser/input/CompositionHelper';
|
import { CompositionHelper } from './CompositionHelper';
|
||||||
import { MockRenderService } from 'browser/TestUtils.test';
|
import { MockRenderService } from '../TestUtils.test';
|
||||||
import { MockCoreService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
|
import { MockCoreService, MockBufferService, MockOptionsService } from '../../common/TestUtils.test';
|
||||||
|
|
||||||
describe('CompositionHelper', () => {
|
describe('CompositionHelper', () => {
|
||||||
let compositionHelper: CompositionHelper;
|
let compositionHelper: CompositionHelper;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IRenderService } from 'browser/services/Services';
|
import { IRenderService } from '../services/Services';
|
||||||
import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
|
import { IBufferService, ICoreService, IOptionsService } from '../../common/services/Services';
|
||||||
import { C0 } from 'common/data/EscapeSequences';
|
import { C0 } from '../../common/data/EscapeSequences';
|
||||||
|
|
||||||
interface IPosition {
|
interface IPosition {
|
||||||
start: number;
|
start: number;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user