Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b3af5aa8 | ||
|
|
5f7eb6e51d |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "xterm.js",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18-buster",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {} // yarn
|
||||
},
|
||||
"forwardPorts": [
|
||||
3000
|
||||
],
|
||||
"postCreateCommand": "yarn install",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"hbenl.vscode-mocha-test-adapter"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.git/
|
||||
@@ -1,264 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"demo/tsconfig.json",
|
||||
"src/browser/tsconfig.json",
|
||||
"src/common/tsconfig.json",
|
||||
"src/headless/tsconfig.json",
|
||||
"test/api/tsconfig.json",
|
||||
"test/benchmark/tsconfig.json",
|
||||
"test/playwright/tsconfig.json",
|
||||
"addons/addon-attach/src/tsconfig.json",
|
||||
"addons/addon-attach/test/tsconfig.json",
|
||||
"addons/addon-canvas/src/tsconfig.json",
|
||||
"addons/addon-canvas/test/tsconfig.json",
|
||||
"addons/addon-fit/src/tsconfig.json",
|
||||
"addons/addon-fit/test/tsconfig.json",
|
||||
"addons/addon-image/src/tsconfig.json",
|
||||
"addons/addon-image/test/tsconfig.json",
|
||||
"addons/addon-ligatures/src/tsconfig.json",
|
||||
"addons/addon-search/src/tsconfig.json",
|
||||
"addons/addon-search/test/tsconfig.json",
|
||||
"addons/addon-serialize/src/tsconfig.json",
|
||||
"addons/addon-serialize/test/tsconfig.json",
|
||||
"addons/addon-serialize/benchmark/tsconfig.json",
|
||||
"addons/addon-unicode11/src/tsconfig.json",
|
||||
"addons/addon-unicode11/test/tsconfig.json",
|
||||
"addons/addon-unicode-graphemes/src/tsconfig.json",
|
||||
"addons/addon-unicode-graphemes/test/tsconfig.json",
|
||||
"addons/addon-unicode-graphemes/benchmark/tsconfig.json",
|
||||
"addons/addon-web-links/src/tsconfig.json",
|
||||
"addons/addon-web-links/test/tsconfig.json",
|
||||
"addons/addon-webgl/src/tsconfig.json",
|
||||
"addons/addon-webgl/test/tsconfig.json"
|
||||
],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"addons/*/src/third-party/*.ts",
|
||||
"out/*",
|
||||
"out-test/*",
|
||||
"**/inwasm-sdks/*",
|
||||
"**/typings/*.d.ts",
|
||||
"**/node_modules",
|
||||
"**/*.js"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"jsdoc"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
"default": "array",
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/consistent-type-assertions": "warn",
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"warn",
|
||||
{
|
||||
"accessibility": "explicit",
|
||||
"overrides": {
|
||||
"constructors": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{ "selector": "default", "format": ["camelCase"],
|
||||
"filter": {
|
||||
"regex": "^[a-z]",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
// variableLike
|
||||
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
|
||||
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
|
||||
// memberLike
|
||||
{ "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
|
||||
{ "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
|
||||
{ "selector": "enumMember", "format": ["UPPER_CASE"] },
|
||||
// memberLike - Allow enum-like objects to use UPPER_CASE
|
||||
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"],
|
||||
"filter": {
|
||||
"regex": "^[a-z]",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
// restrict on* naming for events only
|
||||
{ "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
|
||||
} },
|
||||
// typeLike
|
||||
{ "selector": "typeLike", "format": ["PascalCase"] },
|
||||
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
|
||||
],
|
||||
"@typescript-eslint/no-confusing-void-expression": [
|
||||
"warn",
|
||||
{ "ignoreArrowShorthand": true }
|
||||
],
|
||||
"@typescript-eslint/no-useless-constructor": "warn",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
{
|
||||
"objects": "never",
|
||||
"arrays": "never",
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"curly": [
|
||||
"warn",
|
||||
"multi-line"
|
||||
],
|
||||
"eol-last": "warn",
|
||||
"eqeqeq": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"jsdoc/check-alignment": 1,
|
||||
"jsdoc/check-param-names": 1,
|
||||
"jsdoc/no-multi-asterisks": 1,
|
||||
"keyword-spacing": "warn",
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"code": 1000, // Don't enforce for code
|
||||
"comments": 100,
|
||||
"ignoreTrailingComments": true,
|
||||
"ignoreUrls": true,
|
||||
"ignorePattern": "^ *((?<vt_comment>(//|\\*) @vt)|(?<table_comment>\\* \\| )|(?<commented_code>// ))"
|
||||
}
|
||||
],
|
||||
"new-parens": "warn",
|
||||
"no-duplicate-imports": "warn",
|
||||
"no-else-return": [
|
||||
"warn",
|
||||
{
|
||||
"allowElseIf": false
|
||||
}
|
||||
],
|
||||
"no-eval": "warn",
|
||||
"no-irregular-whitespace": "warn",
|
||||
"no-restricted-imports": [
|
||||
"warn",
|
||||
{
|
||||
"patterns": [
|
||||
".*\\/out\\/.*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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."
|
||||
}
|
||||
],
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-unused-vars": ["warn", {
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}],
|
||||
"no-var": "warn",
|
||||
"one-var": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"object-curly-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"prefer-const": "warn",
|
||||
"spaced-comment": [
|
||||
"warn",
|
||||
"always",
|
||||
{
|
||||
"markers": ["/"],
|
||||
"exceptions": ["-"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/*.api.ts",
|
||||
"**/*.test.ts"
|
||||
],
|
||||
"rules": {
|
||||
"object-curly-spacing": "off",
|
||||
"max-len": "off",
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"jsdoc"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
"default": "array",
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{ "selector": "typeLike", "format": ["PascalCase"] },
|
||||
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
{
|
||||
"objects": "never",
|
||||
"arrays": "never",
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"curly": [
|
||||
"warn",
|
||||
"multi-line"
|
||||
],
|
||||
"eol-last": "warn",
|
||||
"eqeqeq": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"jsdoc/check-alignment": 1,
|
||||
"jsdoc/check-param-names": 1,
|
||||
"keyword-spacing": "warn",
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"code": 1000, // Don't enforce for code
|
||||
"comments": 80,
|
||||
"ignoreUrls": true,
|
||||
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
|
||||
}
|
||||
],
|
||||
"no-irregular-whitespace": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"object-curly-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"spaced-comment": [
|
||||
"warn",
|
||||
"always",
|
||||
{
|
||||
"markers": ["/"],
|
||||
"exceptions": ["-"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: FAQ
|
||||
url: https://github.com/xtermjs/xterm.js/wiki/FAQ
|
||||
about: See our frequently asked questions before filing a bug report
|
||||
- name: Support / Q&A
|
||||
url: https://github.com/xtermjs/xterm.js/discussions/categories/q-a
|
||||
about: Use GitHub Discussions for community support and general Q&A
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Question
|
||||
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
|
||||
---
|
||||
|
||||
🛑 The issue tracker is not for questions 🛑
|
||||
|
||||
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
|
||||
@@ -1,312 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build
|
||||
run: yarn setup
|
||||
- name: Zip artifacts
|
||||
run: |
|
||||
zip -r compressed-build \
|
||||
./out/* \
|
||||
./out-test/* \
|
||||
./addons/addon-attach/out/* \
|
||||
./addons/addon-attach/out-test/* \
|
||||
./addons/addon-canvas/out/* \
|
||||
./addons/addon-canvas/out-test/* \
|
||||
./addons/addon-fit/out/* \
|
||||
./addons/addon-fit/out-test/* \
|
||||
./addons/addon-image/out/* \
|
||||
./addons/addon-image/out-test/* \
|
||||
./addons/addon-ligatures/out/* \
|
||||
./addons/addon-ligatures/out-test/* \
|
||||
./addons/addon-search/out/* \
|
||||
./addons/addon-search/out-test/* \
|
||||
./addons/addon-serialize/out/* \
|
||||
./addons/addon-serialize/out-test/* \
|
||||
./addons/addon-unicode11/out/* \
|
||||
./addons/addon-unicode11/out-test/* \
|
||||
./addons/addon-unicode-graphemes/out/* \
|
||||
./addons/addon-unicode-graphemes/out-test/* \
|
||||
./addons/addon-web-links/out/* \
|
||||
./addons/addon-web-links/out-test/* \
|
||||
./addons/addon-webgl/out/* \
|
||||
./addons/addon-webgl/out-test/*
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: compressed-build.zip
|
||||
if-no-files-found: error
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Lint code
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
run: yarn lint
|
||||
- name: Lint API
|
||||
run: yarn lint-api
|
||||
|
||||
test-unit-coverage:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Unit test coverage
|
||||
run: |
|
||||
yarn test-unit-coverage --forbid-only
|
||||
EXIT_CODE=$?
|
||||
./node_modules/.bin/nyc report --reporter=cobertura
|
||||
exit $EXIT_CODE
|
||||
|
||||
test-unit-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18]
|
||||
runs-on: [ubuntu, macos, windows]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Unit tests
|
||||
run: yarn test-unit --forbid-only
|
||||
|
||||
test-api-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [ubuntu] # macos is flaky
|
||||
browser: [chromium, firefox]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Integration tests (${{ matrix.browser }})
|
||||
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
|
||||
|
||||
test-playwright-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [ubuntu] # macos is flaky
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install --with-deps ${{ matrix.browser }}
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Build demo
|
||||
run: yarn build-demo
|
||||
- name: Integration tests (core) # Tests use 50% workers to reduce flakiness
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
|
||||
- name: Integration tests (addon-canvas)
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas
|
||||
- name: Integration tests (addon-webgl)
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
|
||||
|
||||
test-api:
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [windows] # macos is flaky
|
||||
browser: [chromium, firefox]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Integration tests (${{ matrix.browser }})
|
||||
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
|
||||
|
||||
release-dry-run:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Package headless
|
||||
run: |
|
||||
yarn package-headless
|
||||
node ./bin/package_headless.js
|
||||
- name: Publish to npm (dry run)
|
||||
run: node ./bin/publish.js --dry
|
||||
@@ -1,35 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '41 17 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
# If a commit reaches master, assume it has passed CI via PR and publish
|
||||
# without running tests to save time to publish
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build
|
||||
run: yarn setup
|
||||
- name: Package headless
|
||||
run: |
|
||||
yarn package-headless
|
||||
node ./bin/package_headless.js
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: node ./bin/publish.js
|
||||
@@ -2,9 +2,6 @@ node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
out/
|
||||
out-test/
|
||||
.nyc_output/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
@@ -15,13 +12,15 @@ npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
fixtures/typings-test/*.js
|
||||
package-lock.json
|
||||
|
||||
# Directories needed for code coverage
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Keep bundled code out of Git
|
||||
dist/
|
||||
demo/dist/
|
||||
|
||||
# dont commit benchmark folders
|
||||
.benchmark/
|
||||
timeline/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
NODE_PATH=./out
|
||||
@@ -1,5 +0,0 @@
|
||||
require:
|
||||
- source-map-support/register
|
||||
reporter: spec
|
||||
color: true
|
||||
check-leaks: true
|
||||
@@ -2,8 +2,9 @@
|
||||
*
|
||||
!*/
|
||||
|
||||
# Whitelist - css/
|
||||
!css/**/*.css
|
||||
# Whitelist - entries to be included must be negated with "!"
|
||||
!*.js
|
||||
!*.json
|
||||
|
||||
# Whitelist - dist/
|
||||
!dist/**/*.js
|
||||
@@ -28,15 +29,20 @@
|
||||
|
||||
!src/**/*.css
|
||||
|
||||
# Blacklist - src/ test files
|
||||
src/**/*.test.ts
|
||||
src/**/*.test.d.ts
|
||||
src/**/*.test.js
|
||||
src/**/*.test.js.map
|
||||
|
||||
# Whitelist - typings/
|
||||
!typings/*.d.ts
|
||||
|
||||
# Blacklist - (normal behavior) these will override any whitelist
|
||||
headless/
|
||||
typings/xterm-headless.d.ts
|
||||
*.test.ts
|
||||
*.test.d.ts
|
||||
*.test.js
|
||||
*.test.js.map
|
||||
lib/test/
|
||||
|
||||
docs/
|
||||
/.idea/
|
||||
.vscode/
|
||||
build/
|
||||
fixtures/
|
||||
coverage/
|
||||
demo/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
language: node_js
|
||||
os:
|
||||
- linux
|
||||
node_js:
|
||||
- 6
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq install g++-4.8 ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8 ; fi
|
||||
- npm install -g npm@5.1.0
|
||||
env:
|
||||
matrix:
|
||||
- NPM_COMMAND=tsc
|
||||
- NPM_COMMAND=lint
|
||||
- NPM_COMMAND=test-coverage
|
||||
notifications:
|
||||
email: false
|
||||
script: npm run $NPM_COMMAND
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Unit Tests",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--colors",
|
||||
"--recursive",
|
||||
"${workspaceRoot}/out/**/*.test.js"
|
||||
],
|
||||
"env": {
|
||||
"NODE_PATH": "${workspaceRoot}/out"
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Integration Tests",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--colors",
|
||||
"--recursive",
|
||||
"${workspaceRoot}/out/**/*.api.js"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Demo Client (Chrome)",
|
||||
"url": "http://127.0.0.1:3000",
|
||||
"webRoot": "${workspaceFolder}/"
|
||||
},
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"name": "Demo Client (Edge)",
|
||||
"url": "http://127.0.0.1:3000",
|
||||
"webRoot": "${workspaceFolder}/"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Demo Server",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["start"],
|
||||
"stopOnEntry": true,
|
||||
"runtimeVersion": "16",
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "App listening to (http://.*?:[0-9]+)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
".eslintrc.json.typings": "jsonc"
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"mochaExplorer.envPath": ".mocha.env",
|
||||
"mochaExplorer.files": [
|
||||
"out/**/*.test.js",
|
||||
"addons/**/out/*.test.js"
|
||||
],
|
||||
"mochaExplorer.watch": [
|
||||
"out/**/*.js",
|
||||
"addons/**/out/*.js"
|
||||
]
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"group":{
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"presentation": {
|
||||
"group": "vscode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "start",
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"group": "vscode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Start demo",
|
||||
"dependsOn": ["start", "watch"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"beginsPattern": "assets by",
|
||||
"endsPattern": "webpack \\d+\\.\\d+\\.\\d+ compiled successfully"
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"group": "vscode"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
List of xterm.js contributors. Updated before every release.
|
||||
|
||||
7PH <b.raymond@cern.ch>
|
||||
7PH <b.raymond@protonmail.com>
|
||||
Adrian Labbé <adrilabbelol@gmail.com>
|
||||
Aleksandr Andrienko <aandrienko@codenvy.com>
|
||||
Aleksandr Andriienko <oandriie@redhat.com>
|
||||
Alessandro Nadalin <alessandro.nadalin@gmail.com>
|
||||
Alexander Olsson <noseglid@gmail.com>
|
||||
Alexandre Petit-Pas <petitpasalexandre@gmail.com>
|
||||
Alexey Kontsevoy <alexey@gravitational.com>
|
||||
Andres Mejia <amejia004@gmail.com>
|
||||
Anish Athalye <me@anishathalye.com>
|
||||
Anthony Lapenna <lapenna.anthony@gmail.com>
|
||||
Antonin Stefanutti <antonin@stefanutti.fr>
|
||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||
Anton Skshidlevsky <meefik@gmail.com>
|
||||
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||
Artem Arbatskiy <artyom.arbatskiy@gmail.com>
|
||||
Austin Robertson <austinrobertson@gmail.com>
|
||||
ayapi <colors.aya@gmail.com>
|
||||
Ben Hall <ben@benhall.me.uk>
|
||||
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
|
||||
Benjamin Raymond <b.raymond@cern.ch>
|
||||
Benjamin Woodruff <github@benjam.info>
|
||||
Bill Church <billchurch@users.noreply.github.com>
|
||||
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
|
||||
bottleofwater <nison.mael+bottleofwater@gmail.com>
|
||||
Brandon Bayer <b@bayer.ws>
|
||||
Brian Mock <brian@mockbrian.com>
|
||||
Bruno Ribeiro <b.m.fernandes.ribeiro@gmail.com>
|
||||
Bruno Ribeito <b.m.fernandes.ribeiro@gmail.com>
|
||||
Carson Anderson <carson@betterservers.com>
|
||||
CHaBou <chabup@delean.fr>
|
||||
Christian Budde Christensen <budde377@gmail.com>
|
||||
Christof Marti <chrmarti@microsoft.com>
|
||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||
coderaiser <mnemonic.enemy@gmail.com>
|
||||
Damien Tournoud <damien@platform.sh>
|
||||
Dan Brown <jdanbrown@gmail.com>
|
||||
Daniel Griffen <dagriffe@microsoft.com>
|
||||
Daniel Griffen <daniel@griffen.io>
|
||||
Daniel Imms <daimms@microsoft.com>
|
||||
Daniel Risacher <drisacher@gmail.com>
|
||||
Dan Kaplun <dbkaplun@twitch.tv>
|
||||
Darin Morrison <freebroccolo@users.noreply.github.com>
|
||||
dcylabs <dcylabs@gmail.com>
|
||||
Dominik Csapak <d.csapak@proxmox.com>
|
||||
Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
|
||||
Elliot Saba <staticfloat@gmail.com>
|
||||
Exile <exile@chamber.ee>
|
||||
Felipe Gasper <felipe@felipegasper.com>
|
||||
Felix <30559812+felixse@users.noreply.github.com>
|
||||
ficristo <ficristo.work@gmail.com>
|
||||
Gary Ritchie <gary@rstudio.com>
|
||||
hiro-su <h.sugipon@gmail.com>
|
||||
Ian Lewis <ianlewis@google.com>
|
||||
imoses <ido@twiggle.com>
|
||||
InDieTasten <indietasten@gmail.com>
|
||||
irokas <akasidiari@inf.uth.gr>
|
||||
Jakob Gillich <jakob@gillich.me>
|
||||
Jan Kuri <jkuri88@gmail.com>
|
||||
Jean Bruenn <himself@jeanbruenn.info>
|
||||
Jeremy Danyow <jedanyow@microsoft.com>
|
||||
Jianhui Zhao <jianhuizhao329@gmail.com>
|
||||
Joao Moreno <jomo@microsoft.com>
|
||||
Joao Moreno <mail@joaomoreno.com>
|
||||
Johannes Zellner <johannes@nebulon.de>
|
||||
Jon Austin <jon.i.austin@gmail.com>
|
||||
Jon Kohler <jon@nutanix.com>
|
||||
Jon Masters <jon.masters@sky.com>
|
||||
Jörg Breitbart <jerch@rockborn.de>
|
||||
jpoth <poth.john@gmail.com>
|
||||
Justin Luk <jluk@users.noreply.github.com>
|
||||
Justin Mecham <justin@mecham.me>
|
||||
Kirill Merkushev <lanwen@yandex.ru>
|
||||
Krasimir Tsonev <krasimir@outset.ws>
|
||||
Ledion Bitincka <lbitincka@gmail.com>
|
||||
Linus Unnebäck <linus@folkdatorn.se>
|
||||
Luca <LucaT1@users.noreply.github.com>
|
||||
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
Lukas Drgon <lukas.drgon@gmail.com>
|
||||
Lukas Geiger <lukas.geiger94@gmail.com>
|
||||
Maël Nison <nison.mael@gmail.com>
|
||||
Marc Dumais <marc.dumais@ericsson.com>
|
||||
Marek Libra <mlibra@redhat.com>
|
||||
Markus F.X.J. Oberhumer <markus@oberhumer.com>
|
||||
Martin Chloride <i@martincl2.me>
|
||||
Martin Koppehel <martin.koppehel@st.ovgu.de>
|
||||
Martin Wang <jiahaow@ca.ibm.com>
|
||||
Matt Bierner <matb@microsoft.com>
|
||||
Matthew James <matthew.d.james87@gmail.com>
|
||||
Michael Irwin <mikesir87@gmail.com>
|
||||
Mikko Karvonen <mikko.karvonen@arm.com>
|
||||
mofux <t.zilz@mofux.org>
|
||||
muji <muji@tmpfs.org>
|
||||
Nicolas Ramz <nicolas.ramz@gmail.com>
|
||||
npezza93 <npezza93@gmail.com>
|
||||
Oleksandr Andriienko <oandriie@redhat.com>
|
||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||
Paris Kasidiaris <paris@sourcelair.com>
|
||||
Peng Xiao <pengxiao@outlook.com>
|
||||
Peter Baumgarten <me@peterbaumgarten.com>
|
||||
Philip Olson <philip.olson@protonmail.ch>
|
||||
PowerHat <31401273+7PH@users.noreply.github.com>
|
||||
pro-src <34285059+pro-src@users.noreply.github.com>
|
||||
pro-src <rodneyd.teal@gmail.com>
|
||||
Rick Baker <rick@ricktbaker.com>
|
||||
runarberg <runar@greenqloud.com>
|
||||
Saad Malik <simfox3@gmail.com>
|
||||
Samuel Williams <samuel.williams@oriontransfer.co.nz>
|
||||
Saswat Das <saswatds@users.noreply.github.com>
|
||||
Saul Costa <saul@codevolve.com>
|
||||
Shuanglei Tao <tsl0922@gmail.com>
|
||||
sitzmar <sitzmar@gmail.com>
|
||||
Steven Silvester <steven.silvester@ieee.org>
|
||||
stuicey <stuicey@users.noreply.github.com>
|
||||
Sven-Hendrik Haase <svenstaro@gmail.com>
|
||||
t-amqi <umberos@gmail.com>
|
||||
Thanasis Daglis <thanasis@sourcelair.com>
|
||||
thomas <thomas@kreativkonzentrat.de>
|
||||
Thomas Zilz <t.zilz@mofux.org>
|
||||
Tine Jozelj <tine.jozelj@outlook.com>
|
||||
Tyler Jewell <tjewell@codenvy.com>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
Viraj Sinha <virajosinha@gmail.com>
|
||||
yutaka <shigure@refy.net>
|
||||
YuviPanda <yuvipanda@gmail.com>
|
||||
ZHAO Xudong <zxdong@gmail.com>
|
||||
赵雪珂 <zhaoxueke@onething.net>
|
||||
@@ -23,40 +23,34 @@ opening an issue, read these pointers.
|
||||
- Mention precisely what went wrong. What did you expect to happen? What happened instead? Describe the
|
||||
exact steps a maintainer has to take to make the problem occur.
|
||||
|
||||
- If the problem can not be reproduced in the [demo of xterm.js](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo), please provide an HTML document that demonstrates the problem.
|
||||
- If the problem can not be reproduced in the [demo of xterm.js](README.md#demo), please provide an HTML document that demonstrates the problem.
|
||||
|
||||
- Be polite. Issues with an indignant or belligerent tone tend to be moved to the
|
||||
bottom of the pile.
|
||||
|
||||
## Answering discussion questions
|
||||
|
||||
Issues are only meant to track (likely) feature requests and bugs. We use [GitHub Discussions](https://github.com/xtermjs/xterm.js/discussions) for general Q&A as well as discussing possible features. If you want to help out, many questions get asked over at the [discussions page](https://github.com/xtermjs/xterm.js/discussions) which could use an expert as the core team is often stretched thin.
|
||||
|
||||
## Contributing code
|
||||
|
||||
You can find issues to work on by looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:
|
||||
|
||||
- Make sure you have a [GitHub account](https://github.com/join)
|
||||
- Fork [xterm.js](https://github.com/sourcelair/xterm.js/)
|
||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo)).
|
||||
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running.
|
||||
- Make your changes.
|
||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||
- Make your changes
|
||||
- If your changes are easy to test or likely to regress, add tests. Tests go into `test`, directory.
|
||||
- Follow the general code style of the rest of the project (see below).
|
||||
- Submit a pull request
|
||||
([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
|
||||
Don't put more than one feature/fix in a single pull request.
|
||||
|
||||
By contributing code to xterm.js you:
|
||||
By contributing code to xterm.js you
|
||||
|
||||
- Agree to license the contributed code under xterm.js' [MIT
|
||||
- agree to license the contributed code under xterm.js' [MIT
|
||||
license](LICENSE).
|
||||
|
||||
- Confirm that you have the right to contribute and license the code
|
||||
- confirm that you have the right to contribute and license the code
|
||||
in question. (Either you hold all rights on the code, or the rights
|
||||
holder has explicitly granted the right to use it like this,
|
||||
through a compatible open source license or through a direct
|
||||
agreement with you.)
|
||||
|
||||
### Test coverage
|
||||
### Third party dependencies
|
||||
|
||||
One area that always needs attention is improving out unit test coverage, you can view the code coverage report on [Azure Pipelines](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3) by clicking the Code Coverage tab.
|
||||
We prefer to not include any non-dev third party dependencies in order to keep our code minimal, performant and secure. If you plan on adding a dependency on a third party library it's a good idea to discuss the need in an issue with the maintainers first.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM node:8
|
||||
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Set an entrypoint, to automatically install node modules
|
||||
ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"]
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
# First, install dependencies to improve layer caching
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Add the code
|
||||
COPY . /usr/src/app
|
||||
|
||||
# Run the tests and build, to make sure everything is working nicely
|
||||
RUN npm run build && npm run webpack && npm run test
|
||||
@@ -1,4 +1,3 @@
|
||||
Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
|
||||
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
|
||||
@@ -1,150 +1,136 @@
|
||||
# [](https://xtermjs.org)
|
||||
|
||||
Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
|
||||
[](https://travis-ci.org/xtermjs/xterm.js) [](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) [](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.jsdelivr.com/package/npm/xterm)
|
||||
|
||||
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
||||
|
||||
It enables applications to provide fully featured terminals to their users and create great development experiences.
|
||||
|
||||
## Features
|
||||
|
||||
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim`, and `tmux`, including support for curses-based apps and mouse events.
|
||||
- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer.
|
||||
- **Rich Unicode support**: Supports CJK, emojis, and IMEs.
|
||||
- **Self-contained**: Requires zero dependencies to work.
|
||||
- **Accessible**: Screen reader and minimum contrast ratio support can be turned on.
|
||||
- **And much more**: Links, theming, addons, well documented API, etc.
|
||||
- **Text-based application support**: Use xterm.js to work with applications like `bash`, `git` etc.
|
||||
- **Curses-based application support**: Use xterm.js to work with applications like `vim`, `tmux` etc.
|
||||
- **Mouse events support**: Xterm.js captures mouse events like click and scroll and passes them to the terminal's back-end controlling process
|
||||
- **CJK (Chinese, Japanese, Korean) character support**: Xterm.js renders CJK characters seamlessly
|
||||
- **IME support**: Insert international (including CJK) characters using IME input with your keyboard
|
||||
- **Self-contained library**: Xterm.js works on its own. It does not require any external libraries like jQuery or React to work
|
||||
- **Modular, event-based API**: Lets you build addons and themes with ease
|
||||
|
||||
## What xterm.js is not
|
||||
|
||||
- Xterm.js is not a terminal application that you can download and use on your computer.
|
||||
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output).
|
||||
- Xterm.js is not a terminal application that you can download and use on your computer
|
||||
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output)
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running:
|
||||
First you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/) so you need that installed and then add xterm.js as a dependency by running:
|
||||
|
||||
```bash
|
||||
```
|
||||
npm install xterm
|
||||
```
|
||||
|
||||
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
|
||||
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
|
||||
<script src="node_modules/xterm/lib/xterm.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
|
||||
<script src="node_modules/xterm/dist/xterm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal"></div>
|
||||
<script>
|
||||
var term = new Terminal();
|
||||
term.open(document.getElementById('terminal'));
|
||||
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
|
||||
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
|
||||
|
||||
### Importing
|
||||
|
||||
The recommended way to load xterm.js is via the ES6 module syntax:
|
||||
The proposed way to load xterm.js is via the ES6 module syntax.
|
||||
|
||||
```javascript
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { Terminal } from 'xterm';
|
||||
```
|
||||
|
||||
### Addons
|
||||
|
||||
⚠️ *This section describes the new addon format introduced in v3.14.0, see [here](https://github.com/xtermjs/xterm.js/blob/3.14.2/README.md#addons) for the instructions on the old format*
|
||||
Addons are JavaScript modules that extend the `Terminal` prototype with new methods and attributes to provide additional functionality. There are a handful available in the main repository in the `src/addons` directory and you can even write your own, by using xterm.js' public API.
|
||||
|
||||
Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon, you first need to install it in your project:
|
||||
To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method:
|
||||
|
||||
```bash
|
||||
npm i -S @xterm/addon-web-links
|
||||
```javascript
|
||||
import { Terminal } from xterm;
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
|
||||
|
||||
Terminal.applyAddon(fit);
|
||||
|
||||
var xterm = new Terminal(); // Instantiate the terminal
|
||||
xterm.fit(); // Use the `fit` method, provided by the `fit` addon
|
||||
```
|
||||
|
||||
Then import the addon, instantiate it and call `Terminal.loadAddon`:
|
||||
#### Importing Addons in TypeScript
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
There are currently no typings for addons if they are accessed via extending Terminal prototype, so you will need to upcast if using TypeScript, eg. `(<any>xterm).fit()`.
|
||||
|
||||
const terminal = new Terminal();
|
||||
// Load WebLinksAddon on terminal, this is all that's needed to get web links
|
||||
// working in the terminal.
|
||||
terminal.loadAddon(new WebLinksAddon());
|
||||
Alternatively, you can import addon function and enhance the terminal on demand. This would have better typing support and is friendly to treeshaking. E.g.:
|
||||
|
||||
```typescript
|
||||
import { Terminal } from 'xterm';
|
||||
import { fit } from 'xterm/lib/addons/fit/fit';
|
||||
const xterm = new Terminal();
|
||||
|
||||
// Fit the terminal when necessary:
|
||||
fit(xterm);
|
||||
```
|
||||
|
||||
The xterm.js team maintains the following addons, but anyone can build them:
|
||||
#### Third party addons
|
||||
|
||||
- [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket
|
||||
- [`@xterm/addon-canvas`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas): Renders xterm.js using a `canvas` element's 2d context
|
||||
- [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element
|
||||
- [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support
|
||||
- [`@xterm/addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search): Adds search functionality
|
||||
- [`@xterm/addon-serialize`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize): Serializes the terminal's buffer to a VT sequences or HTML
|
||||
- [`@xterm/addon-unicode11`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11): Updates character widths to their unicode11 values
|
||||
- [`@xterm/addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links): Adds web link detection and interaction
|
||||
- [`@xterm/addon-webgl`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl): Renders xterm.js using a `canvas` element's webgl2 context
|
||||
There are also the following third party addons available:
|
||||
|
||||
- [xterm-webfont](https://www.npmjs.com/package/xterm-webfont)
|
||||
|
||||
## Browser Support
|
||||
|
||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*.
|
||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
|
||||
|
||||
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working.
|
||||
- Chrome latest
|
||||
- Edge latest
|
||||
- Firefox latest
|
||||
- Safari latest
|
||||
- IE11
|
||||
|
||||
### Node.js Support
|
||||
|
||||
We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the serialize addon so it can get all state restored upon reconnection.
|
||||
|
||||
## API
|
||||
|
||||
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
|
||||
|
||||
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
|
||||
|
||||
## Releases
|
||||
|
||||
Xterm.js follows a monthly release cycle roughly.
|
||||
|
||||
All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
||||
|
||||
### Beta builds
|
||||
|
||||
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
|
||||
|
||||
```bash
|
||||
npm install -S xterm@beta
|
||||
```
|
||||
|
||||
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
|
||||
|
||||
## Contributing
|
||||
|
||||
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development.
|
||||
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
|
||||
|
||||
## Real-world uses
|
||||
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
||||
|
||||
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js.
|
||||
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile, and powerful open source code editor that provides an integrated terminal based on xterm.js.
|
||||
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js.
|
||||
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js
|
||||
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js
|
||||
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js
|
||||
- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
|
||||
- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
|
||||
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
|
||||
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by xterm.js.
|
||||
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using xterm.js, socket.io, and ssh2.
|
||||
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
|
||||
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
|
||||
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
|
||||
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
|
||||
- [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
|
||||
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
|
||||
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
|
||||
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
|
||||
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy, and run in the cloud.
|
||||
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
|
||||
- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS.
|
||||
- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job.
|
||||
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scalable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
|
||||
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
|
||||
- [**Portainer**](https://portainer.io): Simple management UI for Docker.
|
||||
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising xterm.js, SJCL & websockets.
|
||||
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
|
||||
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising `xterm.js`, SJCL & websockets.
|
||||
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible
|
||||
computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
|
||||
- [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript.
|
||||
- [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations.
|
||||
- [**Proxmox VE**](https://www.proxmox.com/en/proxmox-ve): Proxmox VE is a complete open-source platform for enterprise virtualization. It uses xterm.js for container terminals and the host shell.
|
||||
@@ -155,81 +141,122 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
|
||||
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
|
||||
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [**rtty**](https://github.com/zhaojh329/rtty): Access your terminals from anywhere via the web.
|
||||
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS.
|
||||
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
|
||||
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
|
||||
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
|
||||
- [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
||||
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users.
|
||||
- [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux
|
||||
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
|
||||
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
|
||||
- [**Hyper**](https://hyper.is): A terminal built on web technologies.
|
||||
- [**Hyper**](https://hyper.is): A terminal built on web technologies
|
||||
- [**Diag**](https://diag.ai): A better way to troubleshoot problems faster. Capture, share and reapply troubleshooting knowledge so you can focus on solving problems that matter.
|
||||
- [**GoTTY**](https://github.com/sorenisanerd/gotty): A simple command line tool that shares your terminal as a web application based on xterm.js.
|
||||
- [**GoTTY**](https://github.com/yudai/gotty): A simple command line tool that shares your terminal as a web application based on xterm.js.
|
||||
- [**genact**](https://github.com/svenstaro/genact): A nonsense activity generator.
|
||||
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
|
||||
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js.
|
||||
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
|
||||
- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.
|
||||
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
|
||||
- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.
|
||||
- [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation.
|
||||
- [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD
|
||||
- [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access.
|
||||
- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems.
|
||||
- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components.
|
||||
- [**NgTerminal**](https://github.com/qwefgh90/ng-terminal): NgTerminal is a web terminal that leverages xterm.js on Angular 7+. You can easily add it into your application by adding `<ng-terminal></ng-terminal>` into your component.
|
||||
- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.
|
||||
- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.
|
||||
- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal
|
||||
- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js
|
||||
- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.
|
||||
- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.
|
||||
- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
|
||||
- [**Dank Domain**](https://www.DDgame.us/): Open source multiuser medieval game supporting old & new terminal emulation.
|
||||
- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio
|
||||
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
|
||||
- [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js
|
||||
- [**Replit**](https://replit.com): Collaborative browser based IDE with support for 50+ different languages.
|
||||
- [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
|
||||
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages are supported, with results displayed by xterm.js.
|
||||
- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services.
|
||||
- [**Commas**](https://github.com/CyanSalt/commas): Commas is a hackable terminal and command runner.
|
||||
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
|
||||
- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH.
|
||||
- [**gifcast**](https://dstein64.github.io/gifcast/): Converts an asciinema cast to an animated GIF.
|
||||
- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js.
|
||||
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
|
||||
- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech).
|
||||
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. Discover more at [tessapp.dev](https://tessapp.dev)
|
||||
- [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js.
|
||||
- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption
|
||||
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
|
||||
- [**goormIDE**](https://ide.goorm.io/): Run almost every programming languages with real-time collaboration, live pair programming, and built-in messenger.
|
||||
- [**FleetDeck**](https://fleetdeck.io): Remote desktop & virtual terminal
|
||||
- [**OpenSumi**](https://github.com/opensumi/core): A framework helps you quickly build Cloud or Desktop IDE products.
|
||||
- [**KubeSail**](https://kubesail.com): The Self-Hosting Company - uses xterm to allow users to exec into kubernetes pods and build github apps
|
||||
- [**WiTTY**](https://github.com/syssecfsu/witty): Web-based interactive terminal emulator that allows users to easily record, share, and replay console sessions.
|
||||
- [**libv86 Terminal Forwarding**](https://github.com/hello-smile6/libv86-terminal-forwarding): Peer-to-peer SSH for the web, using WebRTC via [Bugout](https://github.com/chr15m/bugout) for data transfer and [v86](https://github.com/copy/v86) for web-based virtualization.
|
||||
- [**hack.courses**](https://hack.courses): Interactive Linux and command-line classes using xterm.js to expose a real terminal available for everyone.
|
||||
- [**Render**](https://render.com): Platform-as-a-service for your apps, websites, and databases using xterm.js to provide a command prompt for user containers and for streaming build and runtime logs.
|
||||
- [**CloudTTY**](https://github.com/cloudtty/cloudtty): A Friendly Kubernetes CloudShell (Web Terminal).
|
||||
- [**Go SSH Web Client**](https://github.com/wuchihsu/go-ssh-web-client): A simple SSH web client using Go, WebSocket and Xterm.js.
|
||||
- [**web3os**](https://web3os.sh): A decentralized operating system for the next web
|
||||
- [**Cratecode**](https://cratecode.com): Learn to program for free through interactive online lessons. Cratecode uses xterm.js to give users access to their own Linux environment.
|
||||
- [**Super Terminal**](https://github.com/bugwheels94/super-terminal): It is a http based terminal for developers who dont like repetition and save time.
|
||||
- [**graSSHopper**](https://grasshopper.coding.kiwi): A simple SSH client with file explorer, history and many more features.
|
||||
- [**DomTerm**](https://domterm.org/xtermjs.html): Tiles and tabs. Detachable sessions (like tmux). [Remote connections](https://domterm.org/Remoting-over-ssh.html) using a nice ssh wrapper with predictive echo. Qt, Electron, Tauri/Wry, or desktop browser front-ends. Choose between xterm.js engine (faster) or native DomTerm (more functionality and graphics) - or both.
|
||||
- [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab.
|
||||
- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.
|
||||
- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments
|
||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
## Demo
|
||||
|
||||
Xterm.js ships with a barebones demo implementation, designed for the development and evaluation of the library only. Exposing the demo to the public as is would introduce security risks for the host.
|
||||
|
||||
Below you can find instructions on how to run the demo on different platforms.
|
||||
|
||||
### SourceLair
|
||||
|
||||
SourceLair will run the demo and builder in parallel automatically. Just make sure to choose the "Node.js" project type, when cloning the xterm.js repo (or just use this shortcut; https://lair.io/xtermjs/xtermjs).
|
||||
|
||||
Then open your project's [Public URL](https://help.sourcelair.com/projects/the-public-url/) to access the demo.
|
||||
|
||||
### Docker
|
||||
|
||||
First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer).
|
||||
|
||||
Xterm.js [provides a pre-built Docker image](https://hub.docker.com/r/xtermjs/xterm.js/) to help run the demo easily (Git tags are built as [tagged Docker images](https://hub.docker.com/r/xtermjs/xterm.js/tags/) too).
|
||||
|
||||
To run the just demo (with no editing access). run the following command in your terminal:
|
||||
|
||||
```
|
||||
docker run -p 3000:3000 xtermjs/xterm.js
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||
|
||||
To run the demo and builder in parallel, run the following command in your terminal:
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo. If you prefer a different port than `3000` to access the xterm.js demo, then set the `XTERMJS_PORT` environment variable to the desired port.
|
||||
|
||||
### Foreman (or other Procfile runner)
|
||||
|
||||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
|
||||
|
||||
```
|
||||
npm install
|
||||
foreman start # Replace foreman with "honcho", "forego" etc. depending on your runner
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||
|
||||
### Linux or macOS
|
||||
|
||||
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start # Run this in its own terminal
|
||||
npm run watch # Run this in its own terminal
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser to access the demo.
|
||||
|
||||
### Windows
|
||||
|
||||
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run the following commands in your terminal:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start # Run this in its own terminal
|
||||
npm run watch # Run this in its own terminal
|
||||
```
|
||||
|
||||
Then open http://127.0.0.1:3000 in a web browser to access the demo.
|
||||
|
||||
*Note: Do not use ConEmu, as it seems to break the demo for some reason.*
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are run using the following npm scripts:
|
||||
|
||||
- `npm test`: This will run both unit tests and the linter
|
||||
- `npm run test-suite <file>`: This will run all tests within a particular file, <file> is the test file name excluding the extension (eg. "Linkifier.test")
|
||||
- `npm run test-debug`: This will run unit tests with `--inspect-brk`, this can then be automatically debugged using [VS Code auto attach](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_auto-attach-feature) or manually attached to by a debugger
|
||||
- `npm run test-coverage`: This will run all unit tests and produce a coverage report
|
||||
- `npm run lint`: This will run the linter only
|
||||
|
||||
## Releases
|
||||
|
||||
Xterm.js follows a monthly release cycle roughly.
|
||||
|
||||
The existing releases are available at this GitHub repo's [Releases](https://github.com/sourcelair/xterm.js/releases), while the roadmap is available as [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
||||
|
||||
## Development and Contribution
|
||||
|
||||
Xterm.js is maintained by [SourceLair](https://www.sourcelair.com/) and a few external contributors, but we would love to receive contributions from everyone!
|
||||
|
||||
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) beforehand. The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript/TypeScript and a browser. You will need Node.js installed locally to get all the features working in the demo.
|
||||
|
||||
### Code structure
|
||||
|
||||
`src/` is roughly split up into areas of functionality such as `renderer/` that handles all rendering and `utils/` which provides general utility functions. The `shared/` folder contains code that can be used from either the main thread or a web worker thread, all code inside a `shared/` folder should only ever import other code from a `shared/` folder to minimize the amount of code ran when launching a web worker.
|
||||
|
||||
## License Agreement
|
||||
|
||||
If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||
|
||||
Copyright (c) 2017-2022, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
|
||||
Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
|
||||
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>
|
||||
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,29 +0,0 @@
|
||||
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
|
||||
*
|
||||
!*/
|
||||
|
||||
# Whitelist - lib/
|
||||
!lib/**/*.d.ts
|
||||
|
||||
!lib/**/*.js
|
||||
!lib/**/*.js.map
|
||||
|
||||
!lib/**/*.css
|
||||
|
||||
# Whitelist - src/
|
||||
!src/**/*.ts
|
||||
!src/**/*.d.ts
|
||||
|
||||
!src/**/*.js
|
||||
!src/**/*.js.map
|
||||
|
||||
!src/**/*.css
|
||||
|
||||
# Blacklist - src/ test files
|
||||
src/**/*.test.ts
|
||||
src/**/*.test.d.ts
|
||||
src/**/*.test.js
|
||||
src/**/*.test.js.map
|
||||
|
||||
# Whitelist - typings/
|
||||
!typings/*.d.ts
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,22 +0,0 @@
|
||||
## @xterm/addon-attach
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables attaching to a web socket. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-attach
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { AttachAddon } from '@xterm/addon-attach';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const attachAddon = new AttachAddon(webSocket);
|
||||
terminal.loadAddon(attachAddon);
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-attach/typings/addon-attach.d.ts) for more advanced usage.
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "@xterm/addon-attach",
|
||||
"version": "0.10.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/addon-attach.js",
|
||||
"types": "typings/addon-attach.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
"xterm",
|
||||
"xterm.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2014, 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
*/
|
||||
|
||||
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { AttachAddon as IAttachApi } from '@xterm/addon-attach';
|
||||
|
||||
interface IAttachOptions {
|
||||
bidirectional?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon , IAttachApi {
|
||||
private _socket: WebSocket;
|
||||
private _bidirectional: boolean;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(socket: WebSocket, options?: IAttachOptions) {
|
||||
this._socket = socket;
|
||||
// always set binary type to arraybuffer, we do not handle blobs
|
||||
this._socket.binaryType = 'arraybuffer';
|
||||
this._bidirectional = !(options && options.bidirectional === false);
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._disposables.push(
|
||||
addSocketListener(this._socket, 'message', ev => {
|
||||
const data: ArrayBuffer | string = ev.data;
|
||||
terminal.write(typeof data === 'string' ? data : new Uint8Array(data));
|
||||
})
|
||||
);
|
||||
|
||||
if (this._bidirectional) {
|
||||
this._disposables.push(terminal.onData(data => this._sendData(data)));
|
||||
this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));
|
||||
}
|
||||
|
||||
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
|
||||
this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const d of this._disposables) {
|
||||
d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private _sendData(data: string): void {
|
||||
if (!this._checkOpenSocket()) {
|
||||
return;
|
||||
}
|
||||
this._socket.send(data);
|
||||
}
|
||||
|
||||
private _sendBinary(data: string): void {
|
||||
if (!this._checkOpenSocket()) {
|
||||
return;
|
||||
}
|
||||
const buffer = new Uint8Array(data.length);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
buffer[i] = data.charCodeAt(i) & 255;
|
||||
}
|
||||
this._socket.send(buffer);
|
||||
}
|
||||
|
||||
private _checkOpenSocket(): boolean {
|
||||
switch (this._socket.readyState) {
|
||||
case WebSocket.OPEN:
|
||||
return true;
|
||||
case WebSocket.CONNECTING:
|
||||
throw new Error('Attach addon was loaded before socket was open');
|
||||
case WebSocket.CLOSING:
|
||||
console.warn('Attach addon socket is closing');
|
||||
return false;
|
||||
case WebSocket.CLOSED:
|
||||
throw new Error('Attach addon socket is closed');
|
||||
default:
|
||||
throw new Error('Unexpected socket state');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
|
||||
socket.addEventListener(type, handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
if (!handler) {
|
||||
// Already disposed
|
||||
return;
|
||||
}
|
||||
socket.removeEventListener(type, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
],
|
||||
"paths": {
|
||||
"@xterm/addon-attach": [
|
||||
"../typings/addon-attach.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import WebSocket = require('ws');
|
||||
import { openTerminal, pollFor, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('AttachAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => await page.goto(APP));
|
||||
|
||||
it('string', async function(): Promise<any> {
|
||||
await openTerminal(page);
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
server.on('connection', socket => socket.send('foo'));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
|
||||
it('utf8', async function(): Promise<any> {
|
||||
await openTerminal(page);
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
const data = new Uint8Array([102, 111, 111]);
|
||||
server.on('connection', socket => socket.send(data));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
|
||||
declare module '@xterm/addon-attach' {
|
||||
export interface IAttachOptions {
|
||||
/**
|
||||
* Whether input should be written to the backend. Defaults to `true`.
|
||||
*/
|
||||
bidirectional?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
constructor(socket: WebSocket, options?: IAttachOptions);
|
||||
public activate(terminal: Terminal): void;
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'AttachAddon';
|
||||
const mainFile = 'addon-attach.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,29 +0,0 @@
|
||||
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
|
||||
*
|
||||
!*/
|
||||
|
||||
# Whitelist - lib/
|
||||
!lib/**/*.d.ts
|
||||
|
||||
!lib/**/*.js
|
||||
!lib/**/*.js.map
|
||||
|
||||
!lib/**/*.css
|
||||
|
||||
# Whitelist - src/
|
||||
!src/**/*.ts
|
||||
!src/**/*.d.ts
|
||||
|
||||
!src/**/*.js
|
||||
!src/**/*.js.map
|
||||
|
||||
!src/**/*.css
|
||||
|
||||
# Blacklist - src/ test files
|
||||
src/**/*.test.ts
|
||||
src/**/*.test.d.ts
|
||||
src/**/*.test.js
|
||||
src/**/*.test.js.map
|
||||
|
||||
# Whitelist - typings/
|
||||
!typings/*.d.ts
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,28 +0,0 @@
|
||||
## @xterm/addon-canvas
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+.
|
||||
|
||||
The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/@xterm/addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-canvas
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { CanvasAddon } from '@xterm/addon-canvas';
|
||||
|
||||
const terminal = new Terminal();
|
||||
terminal.open(element);
|
||||
terminal.loadAddon(new CanvasAddon());
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-canvas/typings/addon-canvas.d.ts) for more advanced usage.
|
||||
|
||||
### See also
|
||||
|
||||
- [@xterm/addon-webgl](https://www.npmjs.com/package/@xterm/addon-webgl) A renderer for xterm.js that uses WebGL
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "@xterm/addon-canvas",
|
||||
"version": "0.6.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/addon-canvas.js",
|
||||
"types": "typings/addon-canvas.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
"canvas",
|
||||
"xterm",
|
||||
"xterm.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package",
|
||||
"start-server-only": "node ../../demo/start-server-only"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,499 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ReadonlyColorSet } from 'browser/Types';
|
||||
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
|
||||
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
|
||||
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
|
||||
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
||||
import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { isSafari } from 'common/Platform';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { IRenderLayer } from './Types';
|
||||
|
||||
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
protected _ctx!: CanvasRenderingContext2D;
|
||||
private _deviceCharWidth: number = 0;
|
||||
private _deviceCharHeight: number = 0;
|
||||
private _deviceCellWidth: number = 0;
|
||||
private _deviceCellHeight: number = 0;
|
||||
private _deviceCharLeft: number = 0;
|
||||
private _deviceCharTop: number = 0;
|
||||
|
||||
protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel();
|
||||
private _cellColorResolver: CellColorResolver;
|
||||
private _bitmapGenerator: (BitmapGenerator | undefined)[] = [];
|
||||
|
||||
protected _charAtlas!: ITextureAtlas;
|
||||
protected _charAtlasDisposable = this.register(new MutableDisposable());
|
||||
|
||||
public get canvas(): HTMLCanvasElement { return this._canvas; }
|
||||
public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; }
|
||||
|
||||
private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter<HTMLCanvasElement>());
|
||||
public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private _container: HTMLElement,
|
||||
id: string,
|
||||
zIndex: number,
|
||||
private _alpha: boolean,
|
||||
protected readonly _themeService: IThemeService,
|
||||
protected readonly _bufferService: IBufferService,
|
||||
protected readonly _optionsService: IOptionsService,
|
||||
protected readonly _decorationService: IDecorationService,
|
||||
protected readonly _coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
super();
|
||||
this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService);
|
||||
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
this._canvas.style.zIndex = zIndex.toString();
|
||||
this._initCanvas();
|
||||
this._container.appendChild(this._canvas);
|
||||
this._refreshCharAtlas(this._themeService.colors);
|
||||
this.register(this._themeService.onChangeColors(e => {
|
||||
this._refreshCharAtlas(e);
|
||||
this.reset();
|
||||
// Trigger selection changed as it's handled separately to regular rendering
|
||||
this.handleSelectionChanged(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
|
||||
}));
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
this._canvas.remove();
|
||||
}));
|
||||
}
|
||||
|
||||
private _initCanvas(): void {
|
||||
this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
|
||||
// Draw the background if this is an opaque layer
|
||||
if (!this._alpha) {
|
||||
this._clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
public handleBlur(): void {}
|
||||
public handleFocus(): void {}
|
||||
public handleCursorMove(): void {}
|
||||
public handleGridChanged(startRow: number, endRow: number): void {}
|
||||
|
||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
this._selectionModel.update((this._terminal as any)._core, start, end, columnSelectMode);
|
||||
}
|
||||
|
||||
protected _setTransparency(alpha: boolean): void {
|
||||
// Do nothing when alpha doesn't change
|
||||
if (alpha === this._alpha) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new canvas and replace old one
|
||||
const oldCanvas = this._canvas;
|
||||
this._alpha = alpha;
|
||||
// Cloning preserves properties
|
||||
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
|
||||
this._initCanvas();
|
||||
this._container.replaceChild(this._canvas, oldCanvas);
|
||||
|
||||
// Regenerate char atlas and force a full redraw
|
||||
this._refreshCharAtlas(this._themeService.colors);
|
||||
this.handleGridChanged(0, this._bufferService.rows - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the char atlas, aquiring a new one if necessary.
|
||||
* @param colorSet The color set to use for the char atlas.
|
||||
*/
|
||||
private _refreshCharAtlas(colorSet: ReadonlyColorSet): void {
|
||||
if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr);
|
||||
this._charAtlasDisposable.value = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas);
|
||||
this._charAtlas.warmUp();
|
||||
for (let i = 0; i < this._charAtlas.pages.length; i++) {
|
||||
this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
this._deviceCellWidth = dim.device.cell.width;
|
||||
this._deviceCellHeight = dim.device.cell.height;
|
||||
this._deviceCharWidth = dim.device.char.width;
|
||||
this._deviceCharHeight = dim.device.char.height;
|
||||
this._deviceCharLeft = dim.device.char.left;
|
||||
this._deviceCharTop = dim.device.char.top;
|
||||
this._canvas.width = dim.device.canvas.width;
|
||||
this._canvas.height = dim.device.canvas.height;
|
||||
this._canvas.style.width = `${dim.css.canvas.width}px`;
|
||||
this._canvas.style.height = `${dim.css.canvas.height}px`;
|
||||
|
||||
// Draw the background if this is an opaque layer
|
||||
if (!this._alpha) {
|
||||
this._clearAll();
|
||||
}
|
||||
|
||||
this._refreshCharAtlas(this._themeService.colors);
|
||||
}
|
||||
|
||||
public abstract reset(): void;
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._charAtlas?.clearTexture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
|
||||
* @param x The column to start at.
|
||||
* @param y The row to start at
|
||||
* @param width The number of columns to fill.
|
||||
* @param height The number of rows to fill.
|
||||
*/
|
||||
protected _fillCells(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._deviceCellWidth,
|
||||
y * this._deviceCellHeight,
|
||||
width * this._deviceCellWidth,
|
||||
height * this._deviceCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the
|
||||
* existing fillStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void {
|
||||
const cellOffset = Math.ceil(this._deviceCellHeight * 0.5);
|
||||
this._ctx.fillRect(
|
||||
x * this._deviceCellWidth,
|
||||
(y + 1) * this._deviceCellHeight - cellOffset - this._coreBrowserService.dpr,
|
||||
width * this._deviceCellWidth,
|
||||
this._coreBrowserService.dpr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
|
||||
* existing fillStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._deviceCellWidth,
|
||||
(y + 1) * this._deviceCellHeight + pixelOffset - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */,
|
||||
width * this._deviceCellWidth,
|
||||
this._coreBrowserService.dpr);
|
||||
}
|
||||
|
||||
protected _curlyUnderlineAtCell(x: number, y: number, width: number = 1): void {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
this._ctx.strokeStyle = this._ctx.fillStyle;
|
||||
const lineWidth = this._coreBrowserService.dpr;
|
||||
this._ctx.lineWidth = lineWidth;
|
||||
for (let xOffset = 0; xOffset < width; xOffset++) {
|
||||
const xLeft = (x + xOffset) * this._deviceCellWidth;
|
||||
const xMid = (x + xOffset + 0.5) * this._deviceCellWidth;
|
||||
const xRight = (x + xOffset + 1) * this._deviceCellWidth;
|
||||
const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1;
|
||||
const yMidBot = yMid - lineWidth;
|
||||
const yMidTop = yMid + lineWidth;
|
||||
this._ctx.moveTo(xLeft, yMid);
|
||||
this._ctx.bezierCurveTo(
|
||||
xLeft, yMidBot,
|
||||
xMid, yMidBot,
|
||||
xMid, yMid
|
||||
);
|
||||
this._ctx.bezierCurveTo(
|
||||
xMid, yMidTop,
|
||||
xRight, yMidTop,
|
||||
xRight, yMid
|
||||
);
|
||||
}
|
||||
this._ctx.stroke();
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
protected _dottedUnderlineAtCell(x: number, y: number, width: number = 1): void {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
this._ctx.strokeStyle = this._ctx.fillStyle;
|
||||
const lineWidth = this._coreBrowserService.dpr;
|
||||
this._ctx.lineWidth = lineWidth;
|
||||
this._ctx.setLineDash([lineWidth * 2, lineWidth]);
|
||||
const xLeft = x * this._deviceCellWidth;
|
||||
const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1;
|
||||
this._ctx.moveTo(xLeft, yMid);
|
||||
for (let xOffset = 0; xOffset < width; xOffset++) {
|
||||
// const xLeft = x * this._deviceCellWidth;
|
||||
const xRight = (x + width + xOffset) * this._deviceCellWidth;
|
||||
this._ctx.lineTo(xRight, yMid);
|
||||
}
|
||||
this._ctx.stroke();
|
||||
this._ctx.closePath();
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
protected _dashedUnderlineAtCell(x: number, y: number, width: number = 1): void {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
this._ctx.strokeStyle = this._ctx.fillStyle;
|
||||
const lineWidth = this._coreBrowserService.dpr;
|
||||
this._ctx.lineWidth = lineWidth;
|
||||
this._ctx.setLineDash([lineWidth * 4, lineWidth * 3]);
|
||||
const xLeft = x * this._deviceCellWidth;
|
||||
const xRight = (x + width) * this._deviceCellWidth;
|
||||
const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1;
|
||||
this._ctx.moveTo(xLeft, yMid);
|
||||
this._ctx.lineTo(xRight, yMid);
|
||||
this._ctx.stroke();
|
||||
this._ctx.closePath();
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
|
||||
* existing fillStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._deviceCellWidth,
|
||||
y * this._deviceCellHeight,
|
||||
this._coreBrowserService.dpr * width,
|
||||
this._deviceCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing
|
||||
* strokeStyle on the context.
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
|
||||
const lineWidth = this._coreBrowserService.dpr;
|
||||
this._ctx.lineWidth = lineWidth;
|
||||
this._ctx.strokeRect(
|
||||
x * this._deviceCellWidth + lineWidth / 2,
|
||||
y * this._deviceCellHeight + (lineWidth / 2),
|
||||
width * this._deviceCellWidth - lineWidth,
|
||||
(height * this._deviceCellHeight) - lineWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire canvas.
|
||||
*/
|
||||
protected _clearAll(): void {
|
||||
if (this._alpha) {
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
} else {
|
||||
this._ctx.fillStyle = this._themeService.colors.background.css;
|
||||
this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears 1+ cells completely.
|
||||
* @param x The column to start at.
|
||||
* @param y The row to start at.
|
||||
* @param width The number of columns to clear.
|
||||
* @param height The number of rows to clear.
|
||||
*/
|
||||
protected _clearCells(x: number, y: number, width: number, height: number): void {
|
||||
if (this._alpha) {
|
||||
this._ctx.clearRect(
|
||||
x * this._deviceCellWidth,
|
||||
y * this._deviceCellHeight,
|
||||
width * this._deviceCellWidth,
|
||||
height * this._deviceCellHeight);
|
||||
} else {
|
||||
this._ctx.fillStyle = this._themeService.colors.background.css;
|
||||
this._ctx.fillRect(
|
||||
x * this._deviceCellWidth,
|
||||
y * this._deviceCellHeight,
|
||||
width * this._deviceCellWidth,
|
||||
height * this._deviceCellHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a truecolor character at the cell. The character will be clipped to
|
||||
* ensure that it fits with the cell, including the cell to the right if it's
|
||||
* a wide character. This uses the existing fillStyle on the context.
|
||||
* @param cell The cell data for the character to draw.
|
||||
* @param x The column to draw at.
|
||||
* @param y The row to draw at.
|
||||
*/
|
||||
protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
|
||||
this._ctx.font = this._getFont(false, false);
|
||||
this._ctx.textBaseline = TEXT_BASELINE;
|
||||
this._clipRow(y);
|
||||
|
||||
// Draw custom characters if applicable
|
||||
let drawSuccess = false;
|
||||
if (this._optionsService.rawOptions.customGlyphs !== false) {
|
||||
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._deviceCellWidth, y * this._deviceCellHeight, this._deviceCellWidth, this._deviceCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr);
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
if (!drawSuccess) {
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._deviceCellWidth + this._deviceCharLeft,
|
||||
y * this._deviceCellHeight + this._deviceCharTop + this._deviceCharHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws one or more characters at a cell. If possible this will draw using
|
||||
* the character atlas to reduce draw time.
|
||||
*/
|
||||
protected _drawChars(cell: ICellData, x: number, y: number): void {
|
||||
const chars = cell.getChars();
|
||||
this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth);
|
||||
|
||||
if (!this._charAtlas) {
|
||||
return;
|
||||
}
|
||||
|
||||
let glyph: IRasterizedGlyph;
|
||||
if (chars && chars.length > 1) {
|
||||
glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
|
||||
} else {
|
||||
glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
|
||||
}
|
||||
if (!glyph.size.x || !glyph.size.y) {
|
||||
return;
|
||||
}
|
||||
this._ctx.save();
|
||||
this._clipRow(y);
|
||||
|
||||
// Draw the image, use the bitmap if it's available
|
||||
|
||||
// HACK: If the canvas doesn't match, delete the generator. It's not clear how this happens but
|
||||
// something is wrong with either the lifecycle of _bitmapGenerator or the page canvases are
|
||||
// swapped out unexpectedly
|
||||
if (this._bitmapGenerator[glyph.texturePage] && this._charAtlas.pages[glyph.texturePage].canvas !== this._bitmapGenerator[glyph.texturePage]!.canvas) {
|
||||
this._bitmapGenerator[glyph.texturePage]?.bitmap?.close();
|
||||
delete this._bitmapGenerator[glyph.texturePage];
|
||||
}
|
||||
|
||||
if (this._charAtlas.pages[glyph.texturePage].version !== this._bitmapGenerator[glyph.texturePage]?.version) {
|
||||
if (!this._bitmapGenerator[glyph.texturePage]) {
|
||||
this._bitmapGenerator[glyph.texturePage] = new BitmapGenerator(this._charAtlas.pages[glyph.texturePage].canvas);
|
||||
}
|
||||
this._bitmapGenerator[glyph.texturePage]!.refresh();
|
||||
this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version;
|
||||
}
|
||||
this._ctx.drawImage(
|
||||
this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas,
|
||||
glyph.texturePosition.x,
|
||||
glyph.texturePosition.y,
|
||||
glyph.size.x,
|
||||
glyph.size.y,
|
||||
x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x,
|
||||
y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y,
|
||||
glyph.size.x,
|
||||
glyph.size.y
|
||||
);
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
|
||||
* @param y The row to clip.
|
||||
*/
|
||||
private _clipRow(y: number): void {
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(
|
||||
0,
|
||||
y * this._deviceCellHeight,
|
||||
this._bufferService.cols * this._deviceCellWidth,
|
||||
this._deviceCellHeight);
|
||||
this._ctx.clip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current font.
|
||||
* @param isBold If we should use the bold fontWeight.
|
||||
*/
|
||||
protected _getFont(isBold: boolean, isItalic: boolean): string {
|
||||
const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight;
|
||||
const fontStyle = isItalic ? 'italic' : '';
|
||||
|
||||
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * this._coreBrowserService.dpr}px ${this._optionsService.rawOptions.fontFamily}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch
|
||||
* the operation as window.createImageBitmap is asynchronous.
|
||||
*/
|
||||
const GLYPH_BITMAP_COMMIT_DELAY = 100;
|
||||
|
||||
const enum BitmapGeneratorState {
|
||||
IDLE = 0,
|
||||
GENERATING = 1,
|
||||
GENERATING_INVALID = 2
|
||||
}
|
||||
|
||||
class BitmapGenerator {
|
||||
private _state: BitmapGeneratorState = BitmapGeneratorState.IDLE;
|
||||
private _commitTimeout: number | undefined = undefined;
|
||||
private _bitmap: ImageBitmap | undefined = undefined;
|
||||
public get bitmap(): ImageBitmap | undefined { return this._bitmap; }
|
||||
public version: number = -1;
|
||||
|
||||
constructor(public readonly canvas: HTMLCanvasElement) {
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
// Clear the bitmap immediately as it's stale
|
||||
this._bitmap?.close();
|
||||
this._bitmap = undefined;
|
||||
// Disable ImageBitmaps on Safari because of https://bugs.webkit.org/show_bug.cgi?id=149990
|
||||
if (isSafari) {
|
||||
return;
|
||||
}
|
||||
if (this._commitTimeout === undefined) {
|
||||
this._commitTimeout = window.setTimeout(() => this._generate(), GLYPH_BITMAP_COMMIT_DELAY);
|
||||
}
|
||||
if (this._state === BitmapGeneratorState.GENERATING) {
|
||||
this._state = BitmapGeneratorState.GENERATING_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
private _generate(): void {
|
||||
if (this._state === BitmapGeneratorState.IDLE) {
|
||||
this._bitmap?.close();
|
||||
this._bitmap = undefined;
|
||||
this._state = BitmapGeneratorState.GENERATING;
|
||||
window.createImageBitmap(this.canvas).then(bitmap => {
|
||||
if (this._state === BitmapGeneratorState.GENERATING_INVALID) {
|
||||
this.refresh();
|
||||
} else {
|
||||
this._bitmap = bitmap;
|
||||
}
|
||||
this._state = BitmapGeneratorState.IDLE;
|
||||
});
|
||||
if (this._commitTimeout) {
|
||||
this._commitTimeout = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||
import type { CanvasAddon as ICanvasApi } from '@xterm/addon-canvas';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { setTraceLogger } from 'common/services/LogService';
|
||||
import { IBufferService, IDecorationService, ILogService } from 'common/services/Services';
|
||||
import { CanvasRenderer } from './CanvasRenderer';
|
||||
|
||||
export class CanvasAddon extends Disposable implements ITerminalAddon , ICanvasApi {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: CanvasRenderer;
|
||||
|
||||
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
|
||||
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
|
||||
private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter<HTMLCanvasElement>());
|
||||
public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderer?.textureAtlas;
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
const core = (terminal as any)._core as ITerminal;
|
||||
if (!terminal.element) {
|
||||
this.register(core.onWillOpen(() => this.activate(terminal)));
|
||||
return;
|
||||
}
|
||||
|
||||
this._terminal = terminal;
|
||||
const coreService = core.coreService;
|
||||
const optionsService = core.optionsService;
|
||||
const screenElement = core.screenElement!;
|
||||
const linkifier = core.linkifier!;
|
||||
|
||||
const unsafeCore = core as any;
|
||||
const bufferService: IBufferService = unsafeCore._bufferService;
|
||||
const renderService: IRenderService = unsafeCore._renderService;
|
||||
const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService;
|
||||
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
||||
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
||||
const decorationService: IDecorationService = unsafeCore._decorationService;
|
||||
const logService: ILogService = unsafeCore._logService;
|
||||
const themeService: IThemeService = unsafeCore._themeService;
|
||||
|
||||
// Set trace logger just in case it hasn't been yet which could happen when the addon is
|
||||
// bundled separately to the core module
|
||||
setTraceLogger(logService);
|
||||
|
||||
this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService);
|
||||
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
|
||||
this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));
|
||||
renderService.setRenderer(this._renderer);
|
||||
renderService.handleResize(bufferService.cols, bufferService.rows);
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
renderService.setRenderer((this._terminal as any)._core._createRenderer());
|
||||
renderService.handleResize(terminal.cols, terminal.rows);
|
||||
this._renderer?.dispose();
|
||||
this._renderer = undefined;
|
||||
}));
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderer?.clearTextureAtlas();
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkifier2 } from 'browser/Types';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache';
|
||||
import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver';
|
||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||
import { LinkRenderLayer } from './LinkRenderLayer';
|
||||
import { SelectionRenderLayer } from './SelectionRenderLayer';
|
||||
import { TextRenderLayer } from './TextRenderLayer';
|
||||
import { IRenderLayer } from './Types';
|
||||
|
||||
export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _devicePixelRatio: number;
|
||||
private _observerDisposable = this.register(new MutableDisposable());
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
private readonly _onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>());
|
||||
public readonly onRequestRedraw = this._onRequestRedraw.event;
|
||||
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
|
||||
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
|
||||
private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter<HTMLCanvasElement>());
|
||||
public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _screenElement: HTMLElement,
|
||||
linkifier2: ILinkifier2,
|
||||
private readonly _bufferService: IBufferService,
|
||||
private readonly _charSizeService: ICharSizeService,
|
||||
private readonly _optionsService: IOptionsService,
|
||||
characterJoinerService: ICharacterJoinerService,
|
||||
coreService: ICoreService,
|
||||
private readonly _coreBrowserService: ICoreBrowserService,
|
||||
decorationService: IDecorationService,
|
||||
private readonly _themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
const allowTransparency = this._optionsService.rawOptions.allowTransparency;
|
||||
this._renderLayers = [
|
||||
new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService),
|
||||
new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService),
|
||||
new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService),
|
||||
new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService)
|
||||
];
|
||||
for (const layer of this._renderLayers) {
|
||||
forwardEvent(layer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas);
|
||||
}
|
||||
this.dimensions = createRenderDimensions();
|
||||
this._devicePixelRatio = this._coreBrowserService.dpr;
|
||||
this._updateDimensions();
|
||||
|
||||
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
||||
this.register(this._coreBrowserService.onWindowChange(w => {
|
||||
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
||||
}));
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
for (const l of this._renderLayers) {
|
||||
l.dispose();
|
||||
}
|
||||
removeTerminalFromCache(this._terminal);
|
||||
}));
|
||||
}
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderLayers[0].cacheCanvas;
|
||||
}
|
||||
|
||||
public handleDevicePixelRatioChange(): void {
|
||||
// If the device pixel ratio changed, the char atlas needs to be regenerated
|
||||
// and the terminal needs to refreshed
|
||||
if (this._devicePixelRatio !== this._coreBrowserService.dpr) {
|
||||
this._devicePixelRatio = this._coreBrowserService.dpr;
|
||||
this.handleResize(this._bufferService.cols, this._bufferService.rows);
|
||||
}
|
||||
}
|
||||
|
||||
public handleResize(cols: number, rows: number): void {
|
||||
// Update character and canvas dimensions
|
||||
this._updateDimensions();
|
||||
|
||||
// Resize all render layers
|
||||
for (const l of this._renderLayers) {
|
||||
l.resize(this.dimensions);
|
||||
}
|
||||
|
||||
// Resize the screen
|
||||
this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`;
|
||||
this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`;
|
||||
}
|
||||
|
||||
public handleCharSizeChanged(): void {
|
||||
this.handleResize(this._bufferService.cols, this._bufferService.rows);
|
||||
}
|
||||
|
||||
public handleBlur(): void {
|
||||
this._runOperation(l => l.handleBlur());
|
||||
}
|
||||
|
||||
public handleFocus(): void {
|
||||
this._runOperation(l => l.handleFocus());
|
||||
}
|
||||
|
||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
this._runOperation(l => l.handleSelectionChanged(start, end, columnSelectMode));
|
||||
// Selection foreground requires a full re-render
|
||||
if (this._themeService.colors.selectionForeground) {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
public handleCursorMove(): void {
|
||||
this._runOperation(l => l.handleCursorMove());
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._runOperation(l => l.reset());
|
||||
}
|
||||
|
||||
private _runOperation(operation: (layer: IRenderLayer) => void): void {
|
||||
for (const l of this._renderLayers) {
|
||||
operation(l);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the refresh loop callback, calling refresh only if a refresh is
|
||||
* necessary before queueing up the next one.
|
||||
*/
|
||||
public renderRows(start: number, end: number): void {
|
||||
for (const l of this._renderLayers) {
|
||||
l.handleGridChanged(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
for (const layer of this._renderLayers) {
|
||||
layer.clearTextureAtlas();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the character and canvas dimensions.
|
||||
*/
|
||||
private _updateDimensions(): void {
|
||||
if (!this._charSizeService.hasValidSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See the WebGL renderer for an explanation of this section.
|
||||
const dpr = this._coreBrowserService.dpr;
|
||||
this.dimensions.device.char.width = Math.floor(this._charSizeService.width * dpr);
|
||||
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr);
|
||||
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
|
||||
this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);
|
||||
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
|
||||
this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2);
|
||||
this.dimensions.device.canvas.height = this._bufferService.rows * this.dimensions.device.cell.height;
|
||||
this.dimensions.device.canvas.width = this._bufferService.cols * this.dimensions.device.cell.width;
|
||||
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr);
|
||||
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr);
|
||||
this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows;
|
||||
this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols;
|
||||
}
|
||||
|
||||
private _setCanvasDevicePixelDimensions(width: number, height: number): void {
|
||||
this.dimensions.device.canvas.height = height;
|
||||
this.dimensions.device.canvas.width = width;
|
||||
// Resize all render layers
|
||||
for (const l of this._renderLayers) {
|
||||
l.resize(this.dimensions);
|
||||
}
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
|
||||
private _requestRedrawViewport(): void {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager';
|
||||
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { MutableDisposable } from 'common/Lifecycle';
|
||||
import { isFirefox } from 'common/Platform';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
interface ICursorState {
|
||||
x: number;
|
||||
y: number;
|
||||
isFocused: boolean;
|
||||
style: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _state: ICursorState;
|
||||
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
|
||||
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this.register(new MutableDisposable());
|
||||
private _cell: ICellData = new CellData();
|
||||
|
||||
constructor(
|
||||
terminal: Terminal,
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
private readonly _onRequestRedraw: IEventEmitter<IRequestRedrawEvent>,
|
||||
bufferService: IBufferService,
|
||||
optionsService: IOptionsService,
|
||||
private readonly _coreService: ICoreService,
|
||||
coreBrowserService: ICoreBrowserService,
|
||||
decorationService: IDecorationService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService);
|
||||
this._state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
isFocused: false,
|
||||
style: '',
|
||||
width: 0
|
||||
};
|
||||
this._cursorRenderers = {
|
||||
'bar': this._renderBarCursor.bind(this),
|
||||
'block': this._renderBlockCursor.bind(this),
|
||||
'underline': this._renderUnderlineCursor.bind(this),
|
||||
'outline': this._renderOutlineCursor.bind(this)
|
||||
};
|
||||
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||
this._handleOptionsChanged();
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
super.resize(dim);
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
isFocused: false,
|
||||
style: '',
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._clearCursor();
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
this._handleOptionsChanged();
|
||||
}
|
||||
|
||||
public handleBlur(): void {
|
||||
this._cursorBlinkStateManager.value?.pause();
|
||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
public handleFocus(): void {
|
||||
this._cursorBlinkStateManager.value?.resume();
|
||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
private _handleOptionsChanged(): void {
|
||||
if (this._optionsService.rawOptions.cursorBlink) {
|
||||
if (!this._cursorBlinkStateManager.value) {
|
||||
this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService);
|
||||
}
|
||||
} else {
|
||||
this._cursorBlinkStateManager.clear();
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
public handleCursorMove(): void {
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
}
|
||||
|
||||
public handleGridChanged(startRow: number, endRow: number): void {
|
||||
if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) {
|
||||
this._render(false);
|
||||
} else {
|
||||
this._cursorBlinkStateManager.value.restartBlinkAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private _render(triggeredByAnimationFrame: boolean): void {
|
||||
// Don't draw the cursor if it's hidden
|
||||
if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
const cursorY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
|
||||
const viewportRelativeCursorY = cursorY - this._bufferService.buffer.ydisp;
|
||||
|
||||
// Don't draw the cursor if it's off-screen
|
||||
if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= this._bufferService.rows) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
// in case cursor.x == cols adjust visual cursor to cols - 1
|
||||
const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
|
||||
this._bufferService.buffer.lines.get(cursorY)!.loadCell(cursorX, this._cell);
|
||||
if (this._cell.content === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._coreBrowserService.isFocused) {
|
||||
this._clearCursor();
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._themeService.colors.cursor.css;
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||
if (cursorInactiveStyle && cursorInactiveStyle !== 'none') {
|
||||
this._cursorRenderers[cursorInactiveStyle](cursorX, viewportRelativeCursorY, this._cell);
|
||||
}
|
||||
this._ctx.restore();
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = cursorStyle;
|
||||
this._state.width = this._cell.getWidth();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't draw the cursor if it's blinking
|
||||
if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._state) {
|
||||
// The cursor is already in the correct spot, don't redraw
|
||||
if (this._state.x === cursorX &&
|
||||
this._state.y === viewportRelativeCursorY &&
|
||||
this._state.isFocused === this._coreBrowserService.isFocused &&
|
||||
this._state.style === this._optionsService.rawOptions.cursorStyle &&
|
||||
this._state.width === this._cell.getWidth()) {
|
||||
return;
|
||||
}
|
||||
this._clearCursor();
|
||||
}
|
||||
|
||||
this._ctx.save();
|
||||
this._cursorRenderers[this._optionsService.rawOptions.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._ctx.restore();
|
||||
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = this._optionsService.rawOptions.cursorStyle;
|
||||
this._state.width = this._cell.getWidth();
|
||||
}
|
||||
|
||||
private _clearCursor(): void {
|
||||
if (this._state) {
|
||||
// Avoid potential rounding errors when browser is Firefox (#4487) or device pixel ratio is
|
||||
// less than 1
|
||||
if (isFirefox || this._coreBrowserService.dpr < 1) {
|
||||
this._clearAll();
|
||||
} else {
|
||||
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
|
||||
}
|
||||
this._state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
isFocused: false,
|
||||
style: '',
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _renderBarCursor(x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._themeService.colors.cursor.css;
|
||||
this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth);
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
private _renderBlockCursor(x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._themeService.colors.cursor.css;
|
||||
this._fillCells(x, y, cell.getWidth(), 1);
|
||||
this._ctx.fillStyle = this._themeService.colors.cursorAccent.css;
|
||||
this._fillCharTrueColor(cell, x, y);
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
private _renderUnderlineCursor(x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._themeService.colors.cursor.css;
|
||||
this._fillBottomLineAtCells(x, y);
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
private _renderOutlineCursor(x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.strokeStyle = this._themeService.colors.cursor.css;
|
||||
this._strokeRectAtCell(x, y, cell.getWidth(), 1);
|
||||
this._ctx.restore();
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
|
||||
import { is256Color } from 'browser/renderer/shared/CharAtlasUtils';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkifierEvent | undefined;
|
||||
|
||||
constructor(
|
||||
terminal: Terminal,
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
linkifier2: ILinkifier2,
|
||||
bufferService: IBufferService,
|
||||
optionsService: IOptionsService,
|
||||
decorationService: IDecorationService,
|
||||
coreBrowserService: ICoreBrowserService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService);
|
||||
|
||||
this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e)));
|
||||
this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e)));
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
super.resize(dim);
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state = undefined;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._clearCurrentLink();
|
||||
}
|
||||
|
||||
private _clearCurrentLink(): void {
|
||||
if (this._state) {
|
||||
this._clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);
|
||||
const middleRowCount = this._state.y2 - this._state.y1 - 1;
|
||||
if (middleRowCount > 0) {
|
||||
this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);
|
||||
}
|
||||
this._clearCells(0, this._state.y2, this._state.x2, 1);
|
||||
this._state = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleShowLinkUnderline(e: ILinkifierEvent): void {
|
||||
if (e.fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._themeService.colors.background.css;
|
||||
} else if (e.fg && is256Color(e.fg)) {
|
||||
// 256 color support
|
||||
this._ctx.fillStyle = this._themeService.colors.ansi[e.fg].css;
|
||||
} else {
|
||||
this._ctx.fillStyle = this._themeService.colors.foreground.css;
|
||||
}
|
||||
|
||||
if (e.y1 === e.y2) {
|
||||
// Single line link
|
||||
this._fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);
|
||||
} else {
|
||||
// Multi-line link
|
||||
this._fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);
|
||||
for (let y = e.y1 + 1; y < e.y2; y++) {
|
||||
this._fillBottomLineAtCells(0, y, e.cols);
|
||||
}
|
||||
this._fillBottomLineAtCells(0, e.y2, e.x2);
|
||||
}
|
||||
this._state = e;
|
||||
}
|
||||
|
||||
private _handleHideLinkUnderline(e: ILinkifierEvent): void {
|
||||
this._clearCurrentLink();
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
|
||||
interface ISelectionState {
|
||||
start?: [number, number];
|
||||
end?: [number, number];
|
||||
columnSelectMode?: boolean;
|
||||
ydisp?: number;
|
||||
}
|
||||
|
||||
export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
private _state!: ISelectionState;
|
||||
|
||||
constructor(
|
||||
terminal: Terminal,
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
bufferService: IBufferService,
|
||||
coreBrowserService: ICoreBrowserService,
|
||||
decorationService: IDecorationService,
|
||||
optionsService: IOptionsService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService);
|
||||
this._clearState();
|
||||
}
|
||||
|
||||
private _clearState(): void {
|
||||
this._state = {
|
||||
start: undefined,
|
||||
end: undefined,
|
||||
columnSelectMode: undefined,
|
||||
ydisp: undefined
|
||||
};
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
super.resize(dim);
|
||||
// On resize use the base render layer's cached selection values since resize clears _state
|
||||
// inside reset.
|
||||
if (this._selectionModel.selectionStart && this._selectionModel.selectionEnd) {
|
||||
this._clearState();
|
||||
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
|
||||
}
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
if (this._state.start && this._state.end) {
|
||||
this._clearState();
|
||||
this._clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
public handleBlur(): void {
|
||||
this.reset();
|
||||
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
|
||||
}
|
||||
|
||||
public handleFocus(): void {
|
||||
this.reset();
|
||||
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
|
||||
}
|
||||
|
||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
super.handleSelectionChanged(start, end, columnSelectMode);
|
||||
this._redrawSelection(start, end, columnSelectMode);
|
||||
}
|
||||
|
||||
private _redrawSelection(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
// Selection has not changed
|
||||
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all selections
|
||||
this._clearAll();
|
||||
|
||||
// Selection does not exist
|
||||
if (!start || !end) {
|
||||
this._clearState();
|
||||
return;
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
|
||||
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
|
||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
||||
this._state.ydisp = this._bufferService.buffer.ydisp;
|
||||
return;
|
||||
}
|
||||
|
||||
this._ctx.fillStyle = (this._coreBrowserService.isFocused
|
||||
? this._themeService.colors.selectionBackgroundTransparent
|
||||
: this._themeService.colors.selectionInactiveBackgroundTransparent).css;
|
||||
|
||||
if (columnSelectMode) {
|
||||
const startCol = start[0];
|
||||
const width = end[0] - startCol;
|
||||
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
|
||||
this._fillCells(startCol, viewportCappedStartRow, width, height);
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
|
||||
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
|
||||
|
||||
// Draw middle rows
|
||||
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
|
||||
this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
|
||||
|
||||
// Draw final row
|
||||
if (viewportCappedStartRow !== viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
||||
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
this._fillCells(0, viewportCappedEndRow, endCol, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Save state for next render
|
||||
this._state.start = [start[0], start[1]];
|
||||
this._state.end = [end[0], end[1]];
|
||||
this._state.columnSelectMode = columnSelectMode;
|
||||
this._state.ydisp = this._bufferService.buffer.ydisp;
|
||||
}
|
||||
|
||||
private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
|
||||
return !this._areCoordinatesEqual(start, this._state.start) ||
|
||||
!this._areCoordinatesEqual(end, this._state.end) ||
|
||||
columnSelectMode !== this._state.columnSelectMode ||
|
||||
ydisp !== this._state.ydisp;
|
||||
}
|
||||
|
||||
private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
|
||||
if (!coord1 || !coord2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return coord1[0] === coord2[0] && coord1[1] === coord2[1];
|
||||
}
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { CharData, ICellData } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { Content, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { GridCache } from './GridCache';
|
||||
|
||||
/**
|
||||
* This CharData looks like a null character, which will forc a clear and render
|
||||
* when the character changes (a regular space ' ' character may not as it's
|
||||
* drawn state is a cleared cell).
|
||||
*/
|
||||
// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
|
||||
|
||||
export class TextRenderLayer extends BaseRenderLayer {
|
||||
private _state: GridCache<CharData>;
|
||||
private _characterWidth: number = 0;
|
||||
private _characterFont: string = '';
|
||||
private _characterOverlapCache: { [key: string]: boolean } = {};
|
||||
private _workCell = new CellData();
|
||||
|
||||
constructor(
|
||||
terminal: Terminal,
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
alpha: boolean,
|
||||
bufferService: IBufferService,
|
||||
optionsService: IOptionsService,
|
||||
private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
decorationService: IDecorationService,
|
||||
coreBrowserService: ICoreBrowserService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService);
|
||||
this._state = new GridCache<CharData>();
|
||||
this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value)));
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
super.resize(dim);
|
||||
|
||||
// Clear the character width cache if the font or width has changed
|
||||
const terminalFont = this._getFont(false, false);
|
||||
if (this._characterWidth !== dim.device.char.width || this._characterFont !== terminalFont) {
|
||||
this._characterWidth = dim.device.char.width;
|
||||
this._characterFont = terminalFont;
|
||||
this._characterOverlapCache = {};
|
||||
}
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state.clear();
|
||||
this._state.resize(this._bufferService.cols, this._bufferService.rows);
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._state.clear();
|
||||
this._clearAll();
|
||||
}
|
||||
|
||||
private _forEachCell(
|
||||
firstRow: number,
|
||||
lastRow: number,
|
||||
callback: (
|
||||
cell: ICellData,
|
||||
x: number,
|
||||
y: number
|
||||
) => void
|
||||
): void {
|
||||
for (let y = firstRow; y <= lastRow; y++) {
|
||||
const row = y + this._bufferService.buffer.ydisp;
|
||||
const line = this._bufferService.buffer.lines.get(row);
|
||||
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
|
||||
for (let x = 0; x < this._bufferService.cols; x++) {
|
||||
line!.loadCell(x, this._workCell);
|
||||
let cell = this._workCell;
|
||||
|
||||
// If true, indicates that the current character(s) to draw were joined.
|
||||
let isJoined = false;
|
||||
let lastCharX = x;
|
||||
|
||||
// The character to the left is a wide character, drawing is owned by
|
||||
// the char at x-1
|
||||
if (cell.getWidth() === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// exit early for NULL and SP
|
||||
// NOTE: commented out due to #4120 (needs a more clever patch to keep things performant)
|
||||
// const code = cell.getCode();
|
||||
// if (code === 0 || code === 32) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// Process any joined character ranges as needed. Because of how the
|
||||
// ranges are produced, we know that they are valid for the characters
|
||||
// and attributes of our input.
|
||||
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
|
||||
isJoined = true;
|
||||
const range = joinedRanges.shift()!;
|
||||
|
||||
// We already know the exact start and end column of the joined range,
|
||||
// so we get the string and width representing it directly
|
||||
cell = new JoinedCellData(
|
||||
this._workCell,
|
||||
line!.translateToString(true, range[0], range[1]),
|
||||
range[1] - range[0]
|
||||
);
|
||||
|
||||
// Skip over the cells occupied by this range in the loop
|
||||
lastCharX = range[1] - 1;
|
||||
}
|
||||
|
||||
// If the character is an overlapping char and the character to the
|
||||
// right is a space, take ownership of the cell to the right. We skip
|
||||
// this check for joined characters because their rendering likely won't
|
||||
// yield the same result as rendering the last character individually.
|
||||
if (!isJoined && this._isOverlapping(cell)) {
|
||||
// If the character is overlapping, we want to force a re-render on every
|
||||
// frame. This is specifically to work around the case where two
|
||||
// overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
|
||||
// space is added. Without this, the first half of `b` would never
|
||||
// get removed, and `a` would not re-render because it thinks it's
|
||||
// already in the correct state.
|
||||
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
|
||||
if (lastCharX < line!.length - 1 && line!.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) {
|
||||
// patch width to 2
|
||||
cell.content &= ~Content.WIDTH_MASK;
|
||||
cell.content |= 2 << Content.WIDTH_SHIFT;
|
||||
// this._clearChar(x + 1, y);
|
||||
// The overlapping char's char data will force a clear and render when the
|
||||
// overlapping char is no longer to the left of the character and also when
|
||||
// the space changes to another character.
|
||||
// this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
callback(
|
||||
cell,
|
||||
x,
|
||||
y
|
||||
);
|
||||
|
||||
x = lastCharX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the background for a specified range of columns. Tries to batch adjacent cells of the
|
||||
* same color together to reduce draw calls.
|
||||
*/
|
||||
private _drawBackground(firstRow: number, lastRow: number): void {
|
||||
const ctx = this._ctx;
|
||||
const cols = this._bufferService.cols;
|
||||
let startX: number = 0;
|
||||
let startY: number = 0;
|
||||
let prevFillStyle: string | null = null;
|
||||
|
||||
ctx.save();
|
||||
|
||||
this._forEachCell(firstRow, lastRow, (cell, x, y) => {
|
||||
// libvte and xterm both draw the background (but not foreground) of invisible characters,
|
||||
// so we should too.
|
||||
let nextFillStyle = null; // null represents default background color
|
||||
|
||||
if (cell.isInverse()) {
|
||||
if (cell.isFgDefault()) {
|
||||
nextFillStyle = this._themeService.colors.foreground.css;
|
||||
} else if (cell.isFgRGB()) {
|
||||
nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
|
||||
} else {
|
||||
nextFillStyle = this._themeService.colors.ansi[cell.getFgColor()].css;
|
||||
}
|
||||
} else if (cell.isBgRGB()) {
|
||||
nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
|
||||
} else if (cell.isBgPalette()) {
|
||||
nextFillStyle = this._themeService.colors.ansi[cell.getBgColor()].css;
|
||||
}
|
||||
|
||||
// Get any decoration foreground/background overrides, this must be fetched before the early
|
||||
// exist but applied after inverse
|
||||
let isTop = false;
|
||||
this._decorationService.forEachDecorationAtCell(x, this._bufferService.buffer.ydisp + y, undefined, d => {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
return;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
nextFillStyle = d.backgroundColorRGB.css;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
});
|
||||
|
||||
if (prevFillStyle === null) {
|
||||
// This is either the first iteration, or the default background was set. Either way, we
|
||||
// don't need to draw anything.
|
||||
startX = x;
|
||||
startY = y;
|
||||
}
|
||||
|
||||
if (y !== startY) {
|
||||
// our row changed, draw the previous row
|
||||
ctx.fillStyle = prevFillStyle || '';
|
||||
this._fillCells(startX, startY, cols - startX, 1);
|
||||
startX = x;
|
||||
startY = y;
|
||||
} else if (prevFillStyle !== nextFillStyle) {
|
||||
// our color changed, draw the previous characters in this row
|
||||
ctx.fillStyle = prevFillStyle || '';
|
||||
this._fillCells(startX, startY, x - startX, 1);
|
||||
startX = x;
|
||||
startY = y;
|
||||
}
|
||||
|
||||
prevFillStyle = nextFillStyle;
|
||||
});
|
||||
|
||||
// flush the last color we encountered
|
||||
if (prevFillStyle !== null) {
|
||||
ctx.fillStyle = prevFillStyle;
|
||||
this._fillCells(startX, startY, cols - startX, 1);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private _drawForeground(firstRow: number, lastRow: number): void {
|
||||
this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y));
|
||||
}
|
||||
|
||||
public handleGridChanged(firstRow: number, lastRow: number): void {
|
||||
// Resize has not been called yet
|
||||
if (this._state.cache.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._charAtlas) {
|
||||
this._charAtlas.beginFrame();
|
||||
}
|
||||
|
||||
this._clearCells(0, firstRow, this._bufferService.cols, lastRow - firstRow + 1);
|
||||
this._drawBackground(firstRow, lastRow);
|
||||
this._drawForeground(firstRow, lastRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a character is overlapping to the next cell.
|
||||
*/
|
||||
private _isOverlapping(cell: ICellData): boolean {
|
||||
// Only single cell characters can be overlapping, rendering issues can
|
||||
// occur without this check
|
||||
if (cell.getWidth() !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We assume that any ascii character will not overlap
|
||||
if (cell.getCode() < 256) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const chars = cell.getChars();
|
||||
|
||||
// Deliver from cache if available
|
||||
if (this._characterOverlapCache.hasOwnProperty(chars)) {
|
||||
return this._characterOverlapCache[chars];
|
||||
}
|
||||
|
||||
// Setup the font
|
||||
this._ctx.save();
|
||||
this._ctx.font = this._characterFont;
|
||||
|
||||
// Measure the width of the character, but Math.floor it
|
||||
// because that is what the renderer does when it calculates
|
||||
// the character dimensions we are comparing against
|
||||
const overlaps = Math.floor(this._ctx.measureText(chars).width) > this._characterWidth;
|
||||
|
||||
// Restore the original context
|
||||
this._ctx.restore();
|
||||
|
||||
// Cache and return
|
||||
this._characterOverlapCache[chars] = overlaps;
|
||||
return overlaps;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
|
||||
export interface IRequestRedrawEvent {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that IRenderer implementations should emit the refresh event after
|
||||
* rendering rows to the screen.
|
||||
*/
|
||||
export interface IRenderer extends IDisposable {
|
||||
readonly dimensions: IRenderDimensions;
|
||||
|
||||
/**
|
||||
* Fires when the renderer is requesting to be redrawn on the next animation
|
||||
* frame but is _not_ a result of content changing (eg. selection changes).
|
||||
*/
|
||||
readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;
|
||||
|
||||
handleDevicePixelRatioChange(): void;
|
||||
handleResize(cols: number, rows: number): void;
|
||||
handleCharSizeChanged(): void;
|
||||
handleBlur(): void;
|
||||
handleFocus(): void;
|
||||
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
|
||||
handleCursorMove(): void;
|
||||
handleOptionsChanged(): void;
|
||||
clear(): void;
|
||||
renderRows(start: number, end: number): void;
|
||||
clearTextureAtlas?(): void;
|
||||
}
|
||||
|
||||
export interface IRenderLayer extends IDisposable {
|
||||
readonly canvas: HTMLCanvasElement;
|
||||
readonly cacheCanvas: HTMLCanvasElement;
|
||||
|
||||
readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
||||
/**
|
||||
* Called when the terminal loses focus.
|
||||
*/
|
||||
handleBlur(): void;
|
||||
|
||||
/**
|
||||
* Called when the terminal gets focus.
|
||||
*/
|
||||
handleFocus(): void;
|
||||
|
||||
/**
|
||||
* Called when the cursor is moved.
|
||||
*/
|
||||
handleCursorMove(): void;
|
||||
|
||||
/**
|
||||
* Called when the data in the grid has changed (or needs to be rendered
|
||||
* again).
|
||||
*/
|
||||
handleGridChanged(startRow: number, endRow: number): void;
|
||||
|
||||
/**
|
||||
* Calls when the selection changes.
|
||||
*/
|
||||
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
|
||||
|
||||
/**
|
||||
* Resize the render layer.
|
||||
*/
|
||||
resize(dim: IRenderDimensions): void;
|
||||
|
||||
/**
|
||||
* Clear the state of the render layer.
|
||||
*/
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* Clears the texture atlas.
|
||||
*/
|
||||
clearTextureAtlas(): void;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2021"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
],
|
||||
"@xterm/addon-canvas": [
|
||||
"../typings/addon-canvas.d.ts"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../out-test/playwright/SharedRendererTests';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
const ctxWrapper: ISharedRendererTestContext = {
|
||||
value: undefined,
|
||||
skipCanvasExceptions: true
|
||||
} as any;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
ctxWrapper.value = ctx;
|
||||
await ctx.page.evaluate(`
|
||||
window.addon = new window.CanvasAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('Canvas Renderer Integration Tests', () => {
|
||||
// HACK: The tests fail for an unknown reason
|
||||
test.skip(({ browserName }) => browserName === 'webkit');
|
||||
|
||||
injectSharedRendererTests(ctxWrapper);
|
||||
injectSharedRendererTestsStandalone(ctxWrapper, async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.addon = new window.CanvasAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"es6",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../node_modules/@lunapaint/png-codec",
|
||||
"../../../out-test/playwright/TestUtils",
|
||||
"../../../out-test/playwright/SharedRendererTests"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';
|
||||
|
||||
declare module '@xterm/addon-canvas' {
|
||||
/**
|
||||
* An xterm.js addon that provides search functionality.
|
||||
*/
|
||||
export class CanvasAddon implements ITerminalAddon {
|
||||
public textureAtlas?: HTMLCanvasElement;
|
||||
|
||||
/**
|
||||
* An event that is fired when the texture atlas of the renderer changes.
|
||||
*/
|
||||
public readonly onChangeTextureAtlas: IEvent<HTMLCanvasElement>;
|
||||
|
||||
/**
|
||||
* An event that is fired when the a new page is added to the texture atlas.
|
||||
*/
|
||||
public readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
||||
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon.
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Clears the terminal's texture atlas and triggers a redraw.
|
||||
*/
|
||||
public clearTextureAtlas(): void;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'CanvasAddon';
|
||||
const mainFile = 'addon-canvas.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: ['./node_modules'],
|
||||
extensions: [ '.js' ],
|
||||
alias: {
|
||||
common: path.resolve('../../out/common'),
|
||||
browser: path.resolve('../../out/browser')
|
||||
}
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,29 +0,0 @@
|
||||
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
|
||||
*
|
||||
!*/
|
||||
|
||||
# Whitelist - lib/
|
||||
!lib/**/*.d.ts
|
||||
|
||||
!lib/**/*.js
|
||||
!lib/**/*.js.map
|
||||
|
||||
!lib/**/*.css
|
||||
|
||||
# Whitelist - src/
|
||||
!src/**/*.ts
|
||||
!src/**/*.d.ts
|
||||
|
||||
!src/**/*.js
|
||||
!src/**/*.js.map
|
||||
|
||||
!src/**/*.css
|
||||
|
||||
# Blacklist - src/ test files
|
||||
src/**/*.test.ts
|
||||
src/**/*.test.d.ts
|
||||
src/**/*.test.js
|
||||
src/**/*.test.js.map
|
||||
|
||||
# Whitelist - typings/
|
||||
!typings/*.d.ts
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,24 +0,0 @@
|
||||
## @xterm/addon-fit
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables fitting the terminal's dimensions to a containing element. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-fit
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const fitAddon = new FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.open(containerElement);
|
||||
fitAddon.fit();
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-fit/typings/addon-fit.d.ts) for more advanced usage.
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "@xterm/addon-fit",
|
||||
"version": "0.9.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/addon-fit.js",
|
||||
"types": "typings/addon-fit.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
"xterm",
|
||||
"xterm.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import type { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
|
||||
interface ITerminalDimensions {
|
||||
/**
|
||||
* The number of rows in the terminal.
|
||||
*/
|
||||
rows: number;
|
||||
|
||||
/**
|
||||
* The number of columns in the terminal.
|
||||
*/
|
||||
cols: number;
|
||||
}
|
||||
|
||||
const MINIMUM_COLS = 2;
|
||||
const MINIMUM_ROWS = 1;
|
||||
|
||||
export class FitAddon implements ITerminalAddon , IFitApi {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
public dispose(): void {}
|
||||
|
||||
public fit(): void {
|
||||
const dims = this.proposeDimensions();
|
||||
if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core;
|
||||
|
||||
// Force a full render
|
||||
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
|
||||
core._renderService.clear();
|
||||
this._terminal.resize(dims.cols, dims.rows);
|
||||
}
|
||||
}
|
||||
|
||||
public proposeDimensions(): ITerminalDimensions | undefined {
|
||||
if (!this._terminal) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._terminal.element || !this._terminal.element.parentElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core;
|
||||
const dims: IRenderDimensions = core._renderService.dimensions;
|
||||
|
||||
if (dims.css.cell.width === 0 || dims.css.cell.height === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const scrollbarWidth = this._terminal.options.scrollback === 0 ?
|
||||
0 : core.viewport.scrollBarWidth;
|
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
const elementStyle = window.getComputedStyle(this._terminal.element);
|
||||
const elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
|
||||
const geometry = {
|
||||
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
|
||||
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
],
|
||||
"paths": {
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
],
|
||||
"@xterm/addon-fit": [
|
||||
"../typings/addon-fit.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 1024;
|
||||
const height = 768;
|
||||
|
||||
describe('FitAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
afterEach(async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
});
|
||||
|
||||
it('no terminal', async function(): Promise<any> {
|
||||
await page.evaluate(`window.fit = new FitAddon();`);
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
});
|
||||
|
||||
describe('proposeDimensions', () => {
|
||||
afterEach(() => unloadFit());
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.isAbove(dimensions.cols, 85);
|
||||
assert.isBelow(dimensions.cols, 88);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.isAbove(dimensions.cols, 108);
|
||||
assert.isBelow(dimensions.cols, 111);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 2,
|
||||
rows: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('hidden', async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
await page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);
|
||||
await page.evaluate(`window.term = new Terminal()`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await loadFit();
|
||||
const dimensions: { cols: number, rows: number } | undefined = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
// The value of dims will be undefined if the char measure strategy falls back to the DOM
|
||||
// method, so only assert if it's not undefined.
|
||||
if (dimensions) {
|
||||
assert.isAbove(dimensions.cols, 85);
|
||||
assert.isBelow(dimensions.cols, 88);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('fit', () => {
|
||||
afterEach(() => unloadFit());
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.isAbove(cols, 85);
|
||||
assert.isBelow(cols, 88);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.isAbove(cols, 108);
|
||||
assert.isBelow(cols, 111);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
await loadFit(1, 1);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 2);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
|
||||
await page.evaluate(`
|
||||
window.fit = new FitAddon();
|
||||
window.term.loadAddon(window.fit);
|
||||
document.querySelector('#terminal-container').style.width='${width}px';
|
||||
document.querySelector('#terminal-container').style.height='${height}px';
|
||||
`);
|
||||
}
|
||||
|
||||
async function unloadFit(): Promise<void> {
|
||||
await page.evaluate(`window.fit.dispose();`);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
|
||||
declare module '@xterm/addon-fit' {
|
||||
/**
|
||||
* An xterm.js addon that enables resizing the terminal to the dimensions of
|
||||
* its containing element.
|
||||
*/
|
||||
export class FitAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Creates a new fit addon.
|
||||
*/
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Resizes the terminal to the dimensions of its containing element.
|
||||
*/
|
||||
public fit(): void;
|
||||
|
||||
/**
|
||||
* Gets the proposed dimensions that will be used for a fit.
|
||||
*/
|
||||
public proposeDimensions(): ITerminalDimensions | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprepresents the dimensions of a terminal.
|
||||
*/
|
||||
export interface ITerminalDimensions {
|
||||
/**
|
||||
* The number of rows in the terminal.
|
||||
*/
|
||||
rows: number;
|
||||
|
||||
/**
|
||||
* The number of columns in the terminal.
|
||||
*/
|
||||
cols: number;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'FitAddon';
|
||||
const mainFile = 'addon-fit.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
out/
|
||||
out-test/
|
||||
out-worker/
|
||||
.nyc_output/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Keep bundled code out of Git
|
||||
dist/
|
||||
demo/dist/
|
||||
|
||||
# skip benchmark & inwasm folders, except builds
|
||||
.benchmark/
|
||||
timeline/
|
||||
inwasm-sdks/
|
||||
inwasm-builds/
|
||||
!inwasm-builds/**/final.wat
|
||||
!inwasm-builds/**/final.wasm
|
||||
!inwasm-builds/**/definition.json
|
||||
@@ -1,10 +0,0 @@
|
||||
.github
|
||||
fixture
|
||||
overwrite
|
||||
bootstrap.sh
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
test
|
||||
out-test
|
||||
inwasm-sdks
|
||||
inwasm-builds
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2019, 2020, 2021, 2022, 2023 The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,231 +0,0 @@
|
||||
## @xterm/addon-image
|
||||
|
||||
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### Install from npm
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-image
|
||||
```
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { ImageAddon, IImageAddonOptions } from '@xterm/addon-image';
|
||||
|
||||
// customize as needed (showing addon defaults)
|
||||
const customSettings: IImageAddonOptions = {
|
||||
enableSizeReports: true, // whether to enable CSI t reports (see below)
|
||||
pixelLimit: 16777216, // max. pixel size of a single image
|
||||
sixelSupport: true, // enable sixel support
|
||||
sixelScrolling: true, // whether to scroll on image output
|
||||
sixelPaletteLimit: 256, // initial sixel palette size
|
||||
sixelSizeLimit: 25000000, // size limit of a single sixel sequence
|
||||
storageLimit: 128, // FIFO storage limit in MB
|
||||
showPlaceholder: true, // whether to show a placeholder for evicted images
|
||||
iipSupport: true, // enable iTerm IIP support
|
||||
iipSizeLimit: 20000000 // size limit of a single IIP sequence
|
||||
}
|
||||
|
||||
// initialization
|
||||
const terminal = new Terminal();
|
||||
const imageAddon = new ImageAddon(customSettings);
|
||||
terminal.loadAddon(imageAddon);
|
||||
```
|
||||
|
||||
### 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:
|
||||
- getWinSizePixels (CSI 14 t)
|
||||
- getCellSizePixels (CSI 16 t)
|
||||
- getWinSizeChars (CSI 18 t)
|
||||
|
||||
to help applications getting useful terminal metrics for their image preparations. Set `enableSizeReports` in the constructor options to `false`, if you dont want the addon to alter these terminal settings. This is especially useful, if you have very strict security needs not allowing any terminal reports, or deal with `windowOptions` by other means.
|
||||
|
||||
|
||||
### Operation Modes
|
||||
|
||||
- **SIXEL Support**
|
||||
Set by default, change it with `{sixelSupport: true}`.
|
||||
|
||||
- **Scrolling On | Off**
|
||||
By default scrolling is on, thus an image will advance the cursor at the bottom if needed.
|
||||
(see cursor positioning).
|
||||
|
||||
If scrolling is off, the image gets painted from the top left of the current viewport
|
||||
and might be truncated if the image exceeds the viewport size.
|
||||
The cursor position does not change.
|
||||
|
||||
You can customize this behavior with the constructor option `{sixelScrolling: false}`
|
||||
or with `DECSET 80` (off, binary: `\x1b [ ? 80 h`) and
|
||||
`DECRST 80` (on, binary: `\x1b [ ? 80 l`) during runtime.
|
||||
|
||||
- **Cursor Positioning**
|
||||
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.
|
||||
|
||||
- **SIXEL Palette Handling**
|
||||
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
|
||||
The limit can be increased to a maximum of 4096 registers (via `sixelPaletteLimit`).
|
||||
|
||||
The default palette is a mixture of VT340 colors (lower 16 registers), xterm colors (up to 256) and zeros (up to 4096).
|
||||
There is no private/shared palette distinction, palette colors are always carried over from a previous sixel image.
|
||||
Restoring the default palette size and colors is possible with `XTSMGRAPHICS 1 ; 2` (binary: `\x1b[?1;2S`).
|
||||
It gets also restored automatically on RIS and DECSTR.
|
||||
|
||||
Other than on older terminals, the underlying SIXEL library applies colors immediately to individual pixels
|
||||
(*printer mode*), thus it is technically possible to use more colors in one image than the palette has color slots.
|
||||
This feature is called *high-color* in libsixel.
|
||||
|
||||
A terminal wide shared palette mode with late indexed coloring of the output is not supported,
|
||||
therefore palette animations cannot be used.
|
||||
|
||||
- **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.
|
||||
|
||||
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.
|
||||
|
||||
- **IIP Support (iTerm's Inline Image Protocol)**
|
||||
Set by default, change it with `{iipSupport: true}`.
|
||||
|
||||
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
|
||||
- Supported formats: PNG, JPEG and GIF
|
||||
- No animation support.
|
||||
- Image type hinting is not supported (always deducted from data header).
|
||||
- File download is not supported.
|
||||
- Filename gets parsed but not used.
|
||||
- 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 pixel size is restricted by `pixelLimit` (pre- and post resizing).
|
||||
- Size parameter is restricted by `iipSizeLimit`.
|
||||
- Cursor positioning behaves the same as for sixel (see above).
|
||||
|
||||
|
||||
### Storage and Drawing Settings
|
||||
|
||||
The internal storage holds images up to `storageLimit` (in MB, calculated as 4-channel RBGA unpacked, default 100 MB). Once hit images get evicted by FIFO rules. Furthermore images on the alternate buffer will always be erased on buffer changes.
|
||||
|
||||
The addon exposes two properties to interact with the storage limits at runtime:
|
||||
- `storageLimit`
|
||||
Change the value to your needs at runtime. This is especially useful, if you have multiple terminal
|
||||
instances running, that all add to one upper memory limit.
|
||||
- `storageUsage`
|
||||
Inspect the current memory usage of the image storage.
|
||||
|
||||
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`.
|
||||
|
||||
### Image Data Retrieval
|
||||
|
||||
The addon provides the following API endpoints to retrieve raw image data as canvas:
|
||||
|
||||
- `getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined`
|
||||
Returns the canvas containing the original image data (not resized) at the given buffer position.
|
||||
The buffer position is the 0-based absolute index (including scrollback at top).
|
||||
|
||||
- `extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined`
|
||||
Returns a canvas containing the actual single tile image data (maybe resized) at the given buffer position.
|
||||
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.
|
||||
|
||||
### 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:
|
||||
- Incomming 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.
|
||||
- Sequence Parser (terminal)
|
||||
The parser operates on a buffer containing up to 2^17 codepoints (~0.5 MB).
|
||||
- Sequence Handler - Chunk Decoding (addon)
|
||||
Image data chunks are processed immediately by the SIXEL decoder (streamlined). The decoder allocates memory for image
|
||||
pixels as needed. The allowed image size is restricted by `pixelLimit` (default 16M pixels), the decoder holds 2 pixel buffers at maximum during decoding (RGBA, ~128 MB for 16M pixels).
|
||||
- Sequence Handler - Image Finalization (addon)
|
||||
After decoding the final pixel buffer is grabbed by the sequence handler and written to a canvas of the same size (~64 MB for 16M pixels) and added to the storage.
|
||||
- Image Storage (addon)
|
||||
The image storage implements a FIFO cache, that will remove old images, if a new one arrives and `storageLimit` is hit (default 128 MB). The storage holds a canvas with the original image, and may additionally hold resized versions of images after a font rescaling. Both are accounted in `storageUsage` as a rough estimation of _pixels x 4 channels_.
|
||||
|
||||
Following the steps above, a rough estimation of maximum memory usage by the addon can be calculated with these formulas (in bytes):
|
||||
```typescript
|
||||
// storage alone
|
||||
const storageBytes = storageUsage * storageLimit * 1024 * 1024;
|
||||
// decoding alone
|
||||
const decodingBytes = sixelSizeLimit + 2 * (pixelLimit * 4);
|
||||
|
||||
// totals
|
||||
// inactive decoding
|
||||
const totalInactive = storageBytes;
|
||||
// active decoding
|
||||
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.
|
||||
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%).
|
||||
|
||||
_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.
|
||||
|
||||
_How can I adjust the memory usage?_
|
||||
- `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.
|
||||
- `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.
|
||||
- `sixelSizeLimit`
|
||||
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.
|
||||
|
||||
|
||||
### Terminal Interaction
|
||||
|
||||
- Images already on the terminal screen will reshape on font-rescaling to keep the terminal cell coverage intact.
|
||||
This behavior might diverge from other terminals, but is in general the more desired one.
|
||||
- On terminal resize images may expand to the right automatically, if they were right-truncated before.
|
||||
They never expand to the bottom, if they were bottom-truncated before (e.g. from scrolling-off).
|
||||
- Text autowrapping from terminal resize may break and wrap images into multiple parts. This is unfortunate,
|
||||
but cannot be avoided, while sticking to the stronger terminal text-grid mechanics.
|
||||
(Yes images are a second class citizen on a mainly text-driven interface.)
|
||||
- Characters written over an image will erase the image information for affected cells.
|
||||
- Images are painted above BG/FG data not erasing it. More advanced "composition tricks" like painting images
|
||||
below FG/BG data are not possible. (We currently dont hook into BG/FG rendering itself.)
|
||||
- Previous image data at a cell will be erased on new image data. (We currently have no transparency composition.)
|
||||
|
||||
|
||||
### Performance & Latency
|
||||
|
||||
- Performance should be good enough for occasional SIXEL output from REPLs, up to downscaled movies
|
||||
from `mpv` with its SIXEL renderer (tested in the demo). For 3rd party xterm.js integrations this
|
||||
furthermore depends highly on the overall incoming data throughput.
|
||||
- Image processing has a high latency. Most of the latency though is inherited from xterm.js' incoming data route
|
||||
(PTY -> server process -> websocket -> xterm.js async parsing), where every step creates more waiting time.
|
||||
Since we cannot do much about that "long line", keep that in mind when you try to run more demanding applications with realtime drawing and interactive response needs.
|
||||
|
||||
|
||||
### Status
|
||||
|
||||
- Sixel support and image handling in xterm.js is considered beta quality.
|
||||
- IIP support is in alpha stage. Please file a bug for any awkwardities.
|
||||
|
||||
|
||||
### 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
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# sixel endless mode
|
||||
# Should print an endless sine curve, abort with Ctrl-C.
|
||||
|
||||
period=200
|
||||
amplitude=50
|
||||
|
||||
sixels=(@\$ A\$ C\$ G\$ O\$ _\$-)
|
||||
pi=$(echo "scale=10; 4*a(1)" | bc -l)
|
||||
run=true
|
||||
trap run=false INT
|
||||
|
||||
echo -ne "\x1bP0;0;0q\"1;1#1;2;100;0;0#1"
|
||||
y=0
|
||||
while $run
|
||||
do
|
||||
x=$(echo "s(2*${pi}*${y}/${period})*${amplitude}+2*${amplitude}+0.5" | bc -l)
|
||||
echo -ne "!${x%%.*}?${sixels[$((y%6))]}"
|
||||
(( y++ ))
|
||||
done
|
||||
echo -e "\x1b\\"
|
||||
|
Before Width: | Height: | Size: 76 KiB |
@@ -1,52 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# GLF should move the text cursor downwards,
|
||||
# even if no pixels were modified,
|
||||
# when sixel scrolling is on.
|
||||
|
||||
echo -n $'\e[?80l' # Ensure sixel scrolling is on (disable DECSDM)
|
||||
|
||||
clear
|
||||
echo "A test of Sixel GLF (Graphics Line Feed) when sixel scrolling is on"
|
||||
echo
|
||||
|
||||
# Move cursor down three using GLF ("-")
|
||||
echo -e '\x1bPq$-$-$-$-\x1b\\'
|
||||
|
||||
# Show a single sixel line that says, "Your terminal ->"
|
||||
cat <<'EOF'
|
||||
P0;0;0q"1;1;244;21#0;2;0;0;0#1;2;80;80;80#0~~~NFFFN~~~n!4FN!68~NFN!64~bbb!42~rbbbBB!41~$#1???owwwo???O!4wo!68?owo!64?[[[!42?K[[[{{-#0!5~{woBFB`o{}!4~NFBpp!4xpbBF~~~x@@@~~~xxp@@!5~xxp@@b`pxwxpz!17~zpp???!5px!5~NFBpxxwxxpBBN!4~xxp@@b`pxwxpz~x@@@xxp@@pxx@@F!4~xppp@@!8~x@@@pxxwxp@B!5~zxXXXWXX@BF!10~??!24~!10^FNN^~~~$#1!5?BFN{w{]NB@!4?ow{MM!4EM[{w???E}}}???EEM}}!5?EEM}}[]MEFEMC!17?CMM~~~!5ME!5?ow{MEEFEEM{{o!4?EEM}}[]MEFEMC?E}}}EEM}}MEE}}w!4?EMMM}}!8?E}}}MEEFEM}{!5?CEeeefee}{w!10?~~!24?!10_woo_-#0!5~nFFF??FFN!5~wo_FF!4NFb_o!4~{o_!4Ffb??Fn~~~NFF??!4FN!23~_??!5NFFn~~~w__C!7KCC!4~NFF??!4FN!4~N???N~~??Fn~???N~~N!4F??!4Fn~~~F???Fn~~nF??Fn~~p??KMMMEE???Fn~~~N!4F??!4Fn!29~{{}!4~$#1!5?Owww~~wwo!5?FN^ww!4ow[^N!4?BN^!4wW[~~wO???oww~~!4wo!23?^~~!5owwO???F^^z!7rzz!4?oww~~!4wo!4?o~~~o??~~wO?~~~o??o!4w~~!4wO???w~~~wO??Ow~~wO??M~~rpppxx~~~wO???o!4w~~!4wO!29?BB@-#0!25FE!63FE!15FE!77FE!60F$#1!25?@!63?@!15?@!77?@-\
|
||||
EOF
|
||||
|
||||
sleep 1
|
||||
tput cup 2 30
|
||||
|
||||
# Show four lines saying, "<- A genuine VT340 would end here"
|
||||
cat <<'EOF'
|
||||
P0;0;0q"1;1;144;80#0;2;0;0;0#1;2;80;80;80#0~~rpp@@`@BN!25~!11^!5~^^^N^^^!6~!7^N^^^!5~!4^~~~!5^!6~^^^WWW!8~!7^N^^^!8~^^^N^^^!5~$#1??KMM}}]}{o!25?!11_!5?___o___!6?!7_o___!5?!4_???!5_!6?___fff!8?!7_o___!8?___o___-#0~~NB??EFE??@F^!19~B??{!4}{W??}}~~B@?K!5MK??B~~~}???{!4}{??!4~}???~~~}}{??!6~}{{{??!8~}???{!4}{??!4~B@?K!5MK??B~~$#1??o{~~xwx~~}w_!19?{~~B!4@Bf~~@@??{}~r!5pr~~{???@~~~B!4@B~~!4?@~~~???@@B~~!6?@BBB~~!8?@~~~B!4@B~~!4?{}~r!5pr~~{-#0rpoopr~~~zpooopr!17~}{ww!4pwW??!4~}wwprrb!4rpp~~~pooopz~~zpoopz!4~{w!4pxwoopz~~~r!4poo!4pz~~~pooopz~~zpoopz~~}wwprrb!4rpp~~$#1KMNNMK???CMNNNMK!17?@BFF!4MFf~~!4?@FFMKK[!4KMM???MNNNMC??CMNNMC!4?BF!4MEFNNMC???K!4MNN!4MC???MNNNMC??CMNNMC??@FFMKK[!4KMM-#0!32~^NNNKWwww[[MNNN^~!13N!5~NN!5FNN^!10~N!4F!8~NN!4FN^!36~$#1!32?_ooorfFFFbbpooo_?!13o!5?oo!5woo_!10?o!4w!8?oo!4wo_-#0!32~}{o?AM!4~E?_w}}~o_o}}???}}}__!5~}}~fFFFAOw!6~^FB_w{???!6~B??}!4~{??!35~$#1!32?@BN~|p!4?x~^F@@?N^N@@~~~@@@^^!5?@@?Wwww|nF!6?_w{^FB~~~!6?{~~@!4?B~~-#0!36~{_?FB?o}!7~^NN???NN^!6~!8NE?_x!4~poooP@@???X!5~{o?F!4NB_o!35~$#1!36?B^~w{~N@!7?_oo~~~oo_!6?!8ox~^E!4?MNNNm}}~~~e!5?BN~w!4o{^N-#0!38~}}}!11~!8}!8~!6}!13~!6}!9~!4}!38~$#1!38?@@@!11?!8@!8?!6@!13?!6@!9?!4@-#0!5^!5~!5^!5~!7^!6~!4^~~~!5^!6~{www??!11~^^^N^\W???!22~^^^N^^^!6~!7^N^^^!8~^^^N^\W???~~$#1!5_!5?!5_!5?!7_!6?!4_???!5_!6?BFFF~~!11?___o_af~~~!22?___o___!6?!7_o___!8?___o_af~~~-#0}{??CMB@@FM??o{~~B@?{{!4}{w?@~~~}???~~~}}{??!10~??!8~B??{!5}{???!19~B@?K!5MK??B~~~}???{!4}{??!4~B??{!5}{???~~$#1@B~~zp{}}wp~~NB??{}~BB!4@BF~}???@~~~???@@B~~!10?~~!8?{~~B!5@B~~~!19?{}~r!5pr~~{???@~~~B!4@B~~!4?{~~B!5@B~~~-#0~~~woow~}woo{!4~}{wpprbrrpww{!5~{w!4pxwoopz~~~r!4poo!4pz~~~}{ww!5pwooopz!17~}wwprrb!4rpp~~~pooopz~~zpoopz~~}{ww!5pwooopz$#1???FNNF?@FNNB!4?@BFMMK[KKMFFB!5?BF!4MEFNNMC???K!4MNN!4MC???@BFF!5MFNNNMC!17?@FFMKK[!4KMM???MNNNMC??CMNNMC??@BFF!5MFNNNMC-#0!40~n!4F!99~$#1!40?O!4w-#0!10~^^n!29~???b!4rbBF!4~^NFbrrprrbFF^!4~rrbBBFBbrrrbv~~^NFbrrprrbFF^!42~$#1!10?__O!29?~~~[!4K[{w!4?_ow[KKMKK[ww_!4?KK[{{w{[KKK[G??_ow[KKMKK[ww_-#0!9~}{wq!10}!18~N???N^~~^N??N^~~o??H!6XWGG!4~^NN??!4N^!5~o??H!6XWGG!42~$#1!9?@BFL!10@!18?o~~~o_??_o~~o_??N~~u!6efvv!4?_oo~~!4o_!5?N~~u!6efvv-#0!41B!6ABB!6A!5BAAA?!6A!4B!10A!8BAAA?!6A!42B$#1!41?!6@??!6@!5?@@@B!6@!4?!10@!8?@@@B!6@$-\This text should be indented.
|
||||
EOF
|
||||
|
||||
# For more details, please see:
|
||||
# https://github.com/jerch/xterm-addon-image/issues/37
|
||||
|
||||
# By default, sixel is in a 2:1 aspect ratio, which means
|
||||
# every sixel graphics linefeed (GLF) adds another 12 pixels.
|
||||
# With four GLFs, we have five sixel lines = 5 * 12 = 60 pixels.
|
||||
|
||||
# Which text line that ends up on depends upon the height of your
|
||||
# font. On the VT340, the font is 20 pixels high.
|
||||
|
||||
# On a VT340 sixel scrolling is on by default. DEC refers to this by
|
||||
# two different names "sixel scrolling" and its negation, "DECSDM"
|
||||
# (Sixel Display Mode). They control the same thing, so when DECSDM is
|
||||
# on, sixel scrolling is off, and vice versa.
|
||||
|
||||
# When DECSDM is on, the graphic line feeds do not affect the text cursor.
|
||||
|
||||
|
||||
# How hackerb9 created the two sixel test images:
|
||||
#
|
||||
# convert -family "Courier" -style normal -density 72 -pointsize 26 -interline-spacing -12 -gravity center -fill gray80 -background none label:$'A genuine\nVT340\nwould end\n← here ' +trim +dither -colors 2 sixel:- > reference.six
|
||||
#
|
||||
# convert -family "Courier" -style normal -density 72 -pointsize 26 -interline-spacing -12 -gravity center -fill gray80 -background none label:$'Your terminal →' +trim -bordercolor none -border 2 +dither -colors 2 sixel:- > yourterminal.six
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
const sixelEncode = require('../node_modules/sixel/lib/SixelEncoder').image2sixel;
|
||||
const toRGBA8888 = require('../node_modules/sixel/lib/Colors').toRGBA8888;
|
||||
|
||||
function createRect(size, color) {
|
||||
const pixels = new Uint32Array(size * size);
|
||||
pixels.fill(toRGBA8888(...color));
|
||||
return sixelEncode(new Uint8ClampedArray(pixels.buffer), size, size);
|
||||
}
|
||||
|
||||
function createRectMinusOne(size, color) {
|
||||
const pixels = new Uint32Array(size * size);
|
||||
if (size - 1) {
|
||||
const sub = new Uint32Array(size - 1);
|
||||
sub.fill(toRGBA8888(...color));
|
||||
const last = size * (size - 1);
|
||||
for (let y = 0; y < last; y += size) {
|
||||
pixels.set(sub, y);
|
||||
}
|
||||
}
|
||||
return sixelEncode(new Uint8ClampedArray(pixels.buffer), size, size);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// clear + cursor and sixelScrolling off
|
||||
process.stdout.write('\x1b[2J\x1b[?25;80h');
|
||||
|
||||
for (let i = 1; i < 300; ++i) {
|
||||
await new Promise(res => setTimeout(() => {
|
||||
process.stdout.write(createRect(i, [0, 255, 0]));
|
||||
res();
|
||||
}, 5));
|
||||
}
|
||||
for (let i = 299; i >= 1; --i) {
|
||||
await new Promise(res => setTimeout(() => {
|
||||
process.stdout.write(createRectMinusOne(i, [0, 255, 0]));
|
||||
res();
|
||||
}, 5));
|
||||
}
|
||||
|
||||
// re-enable cursor and sixel scrolling
|
||||
process.stdout.write('\x1b[2J\x1b[?25;80l');
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1 +0,0 @@
|
||||
]1337;File=inline=1;size=525;name=Li4vcGFsZXR0ZS5wbmc=:iVBORw0KGgoAAAANSUhEUgAAAoAAAABQCAYAAACJbMQlAAAABmJLR0QA/wD/AP+gvaeTAAABwklEQVR4nO3dwQnEMAwAQedIIem/yVwLBj0C3pm3uAO/Fj2ia631rh3P1lRu7vnof0+Z836zOe83m/N+sznvN5vzfrM57zeb+23+HAAAhxCAAAAxAhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIi59z8tDQDACWwAAQBiBCAAQIwABACIEYAAADECEAAgRgACAMQIQACAGAEIABAjAAEAYlwCAQCIsQEEAIgRgAAAMQIQACBGAAIAxAhAAIAYAQgAECMAAQBiBCAAQIwABACIcQkEACDGBhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIgRgAAAMQIQACDGJRAAgBgbQACAGAEIABAjAAEAYgQgAECMAAQAiBGAAAAxAhAAIEYAAgDECEAAgBiXQAAAYmwAAQBiBCAAQIwABACIEYAAADECEAAgRgACAMQIQACAGAEIABAjAAEAYlwCAQCIsQEEAIgRgAAAMQIQACBGAAIAxAhAAIAYAQgAECMAAQBiBCAAQIwABACIcQkEACDGBhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIgRgAAAMQIQACDmDz4lC02AaqMRAAAAAElFTkSuQmCC
|
||||
@@ -1 +0,0 @@
|
||||
]1337;File=inline=1;size=1667;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYuZ2lm:R0lGODlhSAAwAPcAAAQCBIyKjMzOzERGRHSOvAxCnLy6vNzm9DRmrCQiJGxubNze3HSWxJSq1JyenCRarPz+/FR+vBweHLzG5FxeXGSKxBROpOzu7ERytDw+PHx+fLTC5AwODIyOjExOTAxGnLy+vDxqtCwuLHR2dNTa7ISezKS23KyurMTO5GyKxOzy/ACCXwAAN7AAF+IAjIEAw8DPArtsAOSupIHW2HBvAsUBAHIAbwAAARwAiEdgA/caAr+CAgCIALAjAOMcJIGCAAB8EADGAAByiAAArcQcSrtHBOT3zYG/AaQA7sVgE3Ib9wCCv88AZ2wAAaIAANUAAG+M4wEjogAc9wCCv8SwGbvGBeRyAIEAAF/PrBZsxwCucgDWAIRvpMUBaHIAAQAAAFCMfEIjj/kcUb+CAAC4sLAZKOL3AIG/AECQoAfGowBy9wAAvwBQAABCAAD5UAC/ALgALBlguPcaUb+CAMRwpNUFaAAAAQAAAOAAAPgAAHIAAAAAAG24DBgZAPf3UL+/AM/QAGzWAKIAUNUAAADgfAD4jwByUQAAAKJtQNUYAH/3AFe/AGTPABNsAACuAADWAAAAMgAABUYAAAIAAACuGQDWBQh/AABXAAJH1AABx8QAcgsAAAMAUAAApTdG9wYCv3oAADMAAKUBULAAAEcCZwEApQCM9wDDv0ACAAcAAABfUAAWAKUAQ7AAAEelAAGwAPhHQ8UBAHIAAAAAABhwFIkFxX8AUIEAABClAtKwADxHAIIBABAUopPXKKZm99Iov/gf+MUXx3IAcgAAAAxwZsYFtHIA9wAAvxgAAIlgAH8aUIGCAHiM1QrDtD0C94IAvxAAAJMASqYChdIAgQxw78YFGXIA+AAAvw4AM6EAGvcA+L8AvyYAALMASveNhb+tgQBKLGAEuDzNUYIBAADRmwABbgAA9wAAv8+kFmzYxWYCUNYAAG+k4wHYKAACQgAAAAAAHmAAxTsoUIIAADwcvcZ6bnIC9wAAvxgAm4kAbn8A94EAv0QABAoAAD0MAIIXACH5BAAAAAAALAAAAABIADAABwj/AAEIHEiwoMGDCBMqXGgQgsOHECNKnEixosWLGDNq3Mixo8ePIEOKHCkxgMkRBkiq7BhAgEmXAlbKxBgAwoALAyDUnMlzYs0EA1LuVIGiKAoVEg8YRXFxQoOnJpZKLYpUY0wII2wKGLBg54EPYD+YkEgi7AemFS2AVRuW7Qe3aB8K8ACAA4UFDy+IcGhg54URFx4+CFthIlsLDCqiKGD2bWOzcSE4MOjAoQYAck0GCPyQweGJFcI+qMiA8eO2azFAFIDQwAUOAEBcRNGWhEQTZg9QRFDg8NrTkSUglKBAYOWLbhNHVGG2wcSvYVNE3GC28MPJA0VoUJCAIAfOFiOE/0UwEYPjCBMbmK0quC17CBkGHodwgcJADRlxO9YdMXTY9w6Jx1gIEZXg2AclQHSBfBLZ911GzIU1VkRufTCBRL8595AKbo0G0QkCUUCRdhuFEBZ6EJFQoXUPTWCWbQ/5B9aFEF0GAF4TCQAeRuqFVeBvH3gYo2gQ0eaYahHR5cFKZTm2AUQIPAajQ4OBpZxD5oXFH0TxzUdSlR9c+VWFFiToUJNgPemQfmCxyCUAO44ko5BsukWeQz1aYMGGg7kVwpQOZSDBTC46BqN4bxn4n0MmgoUiBD2iJhaXGfDUnENuHSCeWmNFCNaEEJRQoVmgalDpTIh+oBp1YJEXKXpsfv+wJQol1IqAbxZU5cCpMuHGlgoMrJUYdG9BEJpaBFKEQoVmLsDrSsSKBSaMYC4blpkUoVmsZT1FCVaj20Lg2bcvXnQsWDDKxtO4jbFoZGNCVsTqWxr2BEGTvtGIqaRuZmsWtvaO+sF7Moalr0VuAdxTwR88uuaBYAH43G8pqItRnBkVGla9DnmqlsOK/btkRhnguFGEbG35UJZg1TtBBHdGFCxbKHSA30UBAHCCRywnG1GksjrkrYXLgTnwAgB4KZEAsN3MUaRXQkSszzJaAGiwJzpUnNJywZa0R9BZEBlED6glpltlokDCuW+h9RoAO0EUgNd7fdQnaWBFpqjAWT9gBCIAxJ1wggLCCcSByR0F22+R4a7cWIUIAIjdQRxc9RFtoBq2uAopnPZBCBJL5jVBElgOUq4WVZB5kZt+u3pECygggkAZdICxvbjnrvvuvPfu++/ABy88Q8QXb/zxAgUEADs=
|
||||
@@ -1 +0,0 @@
|
||||
]1337;File=inline=1;size=2159;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYuanBn:/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAwAEgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD33/hXPgn/AKE7w3/4K4P/AImj/hXPgn/oTvDf/grg/wDia8y/aLuNX0PWtB1nS7+8ghcGJ4o5mWPejbhlQcHIYj6LXr07J4l8IO1jM8K6lZboZUbayb0yrAjoRkH8K6J4flpwqX0l+BhGrzSlC2qM7/hXPgn/AKE7w3/4K4P/AImj/hXPgn/oTvDf/grg/wDia81/Zt8S313da3ousXk9xcR7biLz5S7DB2OMknvsrov2ifEEujeB0trSd4bq/uFiDRsVYIvzMQR9FH/AquWDlHELD3+ZMcQnS9qdR/wrnwT/ANCd4b/8FcH/AMTR/wAK58E/9Cd4b/8ABXB/8TXhFr4F8dL4SXX5fFCWOntbfays1/OrqhGRkKpGSMcZzyB1q7+z9D4g8QeKzqF3q2pSaZpwzIsty7LJIwIVME4Pcn0wPUVvPL4xhKaqJqJnHFNyUXFq57V/wrnwT/0J3hv/AMFcH/xNH/CufBP/AEJ3hv8A8FcH/wATXgnxP8YePPiB8Sr/AMI/DKe8tbLSG8u4ntZ/ILSKcOzy5BVQ2VCg87ScHoF0/wAcfE3T9Z8O/D7xJZSNr0mq20635YYuLJG3upZeGHycsOdoYEZ6+Ydh6Pd2fgW38Qvpn/CA+G2EaNKzfYoPMEStIrPs8voDDJ37D+JlUld9ceFNNnvzcyISGfzGjKoQW+Yn5iu8Al3yoYA73yPmbJQBzvx30X+2fhtqRRN01jtvE9tmdx/74L1ynw18Z/Y/gVqN20n+laMktuhJ5y3MX4ZdV/CvZ7qCO6tpredQ8UqGN1PdSMEfrXxPqb6hoD674WDExveKkygHLmJnC4Hvuz74Fexl8FiaToy6NP5dTz8VJ0Zqouqa/wAjb+Ed/P4c8f8Ah++uEeO0v2MIc8K6Oxjzn0DgH/gNdt8bpH8V/FnQ/DFu5KQeXC+P4GlIZz+CbD+FT/Grwf8A2L8NvCk1uu2fSQtvMyccuNzN/wB/F/8AH6p/AeO48V/E/VfE2ohWkgRpiQOFkk+VQPYLvH4V3SqQmnjV0TXzvoc6jKP+zPq0/wDM0P2k/FRt47Twnp+Y4QizXOOAQPuR/QY3H/gNdx8BtQ8PS+CoLDQJibm3G+9jlULIZW6uR3BxgEE8ADqK7nUdG0zUlcajp1pdb12t50KuSPqRXzl4bgTwP+0C2m2EhSwMxgKs/HlSR7wpP+ySp5/u1wUnDE4Z0Y6OKv6nTNSpVlUeqenoej/s26IuneAZdSmX/iY6xf3N3cuw+YkSsgB+gXOOxY16bNY2s17b3k1vE91bh1hlZQWjDY3BT2zgZ+lQ6JpcGj2H2O0GIfOllA9PMkaQgewLEVeJx1ryDvFooooAK8K8X+C/tf7QWjTCPNneKt9KSPl3QjDD8dsf/fdel/8ACxvBP/Q4+G//AAaQf/FVG3j/AMCNcJO3i3wwZ0VkSQ6lBuVWILAHdkA7VyO+B6V0YevKg249U0ZVaSqJJ9Hcu/EDRf8AhIfBesaYF3yT27eUP+mi/Mn/AI8BXm3wa8Panb/CDUbjRZ/sms6mzy28xUEgJ8qDDDAztbntuzXoH/CxvBP/AEOPhv8A8GkH/wAVUdv4/wDAltCkNv4t8MRRIMKialAqj6ANTp4mUKTpLun939IUqKlPn8rHkXg340XnhuwuNJ8Y2N9eX9s77ZWbEuSSdkm70PQjtgY4ql8KdCu/iD4+1LxNrVsTppMpkyPkd3QoI1Pfarfhgetet6n4l+F+qzCbVNa8F3soG0PcXVrIwHpliavW3j7wHawJBa+LPC8MKDCRx6jAqqPQANgV1SxtNRl7KFpS3/4BjHDTuueV0tjg0+LX/CvL5/DfxPivRNCT9i1iKHzI7+AfddgORJjAYAEZ54yM8xd/ETV/jN4v0zw/4Btrux8PWV3DealqU67WZY3DKMAkAZXhc5YgdADXrereMPhxrNr9m1fxF4Qv7bOfKur22lTPrhmIpdK8ZfDnSLRbXSfEfhGxtV5ENtfW0SD/AICrAV5h2HbUVyv/AAsbwT/0OPhv/wAGkH/xVFAH/9k=
|
||||
@@ -1 +0,0 @@
|
||||
]1337;File=inline=1;size=1658;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYucG5n:iVBORw0KGgoAAAANSUhEUgAAAEgAAAAwCAMAAACFQszZAAADAFBMVEUEAgSMiozMzsxERkR0jrwMQpy8urzc5vQ0ZqwkIiRsbmzc3tx0lsSUqtScnpwkWqz8/vxUfrwcHhy8xuRcXlxkisQUTqTs7uxEcrQ8Pjx8fny0wuQMDgyMjoxMTkwMRpy8vrw8arQsLix0dnTU2uyEnsykttysrqzEzuRsisTs8vwAgl8AADewABfiAACBAAlwzwPVbADkrqSB1thwbwLFAQByAG8AAAEcADBHYAf3GgK/ggIA/ACwaADjHByBggAAfA4AxgAAchQAACB0HEbVRwTk98WBvwGkAO7FYBNyG/cAgr/PAGdsAAGiAADVAABvAOMBaaIAHPcAgr90sBXVxgXkcgCBAABfz6wWbMcArnIA1gCEb1TFARNyAAAAAABQANRCaU35HFG/ggAAuLCwGSji9wCBvwD4kKAIxqMAcvcAAL8AUAAAQgAA+VAAvwC4AIQZYHb3GlG/ggDElFTVDxMAAAAAAADgAAD4AAByAAAAAABtuAwYGQD391C/vwDP0ABs1gCiAFDVAAAA4NQA+E0AclEAAACibUDVGAB/9wBXvwBkzwATbAAArgAA1gAAABgAAAVGAAACAAAArhUA1gUIfwAAVwADR9QAAcd0AHIlAAADAFAAAKU3RvcGAr96AAAzAAClAVCwAABHA2cBAKUAAPcACb/4AwAIAAAAX1AAFgClAEOwAABHpQABsAD4R0PFAQByAAAAAAAYlByJD8V/AFCBAAAQpQLSsAA8RwCCAQAQFKKT1yimZvfSKL/4H/jFF8dyAHIAAAAMlGbGD7RyAPcAAL8YAACJYAB/GlCBggB4ANUKCbQ9A/eCAL8QAACTAEqmA4XSAIEMlO/GDxlyAPgAAL8OADOhABr3APi/AL8mAACzAEr3GoW/IIEARoRgBHY8xVGCAQAAz5sAAW4AAPcAAL/PpB5s2MVmAlDWAABvpOMB2CgAAkIAAAAAABZgAMU7KFCCAAA8HL3Gem5yAvcAAL8YAJuJAG5/APeBAL9EAAQKAAA9DACCFwCk+0jYAAAACXBIWXMAAAAAAAAAAACdYiYyAAADIElEQVR4nO2WW3OqQAyAAUehArJWXcDSUiwg3f//A8/msheU0/bBhzNnutMOEZKPJJtkCYJHLfGg9V+CwjD82DwAFEah/oseABKrciXCR4CeVhsEqUkvRXdjkD2tQ57n4+SWuqFEQnyIVbRKARS3eo30oAfZI8m2lXBL8j89it6C5phqobwIsQn19aOE0AqtcfIsM8uZ1q3B0ELQjkp5J8QZzHWywxJ3LQNrNj1p7cKCsrVFAE1W6I9pik3ZBO9GFUATqPX0ewSb2DxN1hSb9B16NqDnLbrlQBBPy/Eo0M/5IWbvE8WX1sSPgV3O2ye4NuUMtNdKCd+o9Ov3LOdgTRsFeZQo1pQeUR61cBYz0AjOxy5JbKxfsG47lAaIbgCpDGw8R88hAimvADAdB5KBj2EqaffgGgRHY3xxDnGvdVqP4umlTYY4gNhbN4l+DoLUGEflLQiTwTFAEIWNEqUJblZk8Ba8iaVFoB40X0BKaJ/Rj8JsZuXVRO12fAFkbWLuiEFwuwB9dNECqPwKZKIYuSESildCwasCb3VUsvXzIseADmDfY0XJgQvA7EBuOgT3ta6/BAkuaHh1vNdWo6uJwfYs/Dp/A4LirrAREnRh77puGoYhwYaD0t59AxrBd5XhEIEWkzptkssa1oRu6T1IvwHRdCto5+GCloNTxD2E3J+XMN5xBAXUsS4MqM6WEy/wEG6830NmoIxTCvUycXILXxPyJ/M7wB2o5wmGHcUj+uRr9u081r+BBHukKIzWGwKewk9AZEwzYKROmR0+MTj5eZOhm3mEC4cGT1mFoe1nNpi44ab1aztSHEhhWrjHKwM97M0MzuDx9Drb/TC43oPQ2FRgzmWd2EypAoNN/SkSNW5oe6DcnSVYnwCFxNFJlXEGt46kOTfHkTWW9rQuJM0nLIRh6rEc4bE+FEOOS3MuYgGkO0NaOePTcPAPbMz+FU7G63UL52STLoIyrwInA63MNNJzgcphZz9km0gsgiZzJMGSDFWfxp/OlNWu4SPb48w//aRXgScLnWDQtZ33knR7CYL6dTa8/6Wv2l/Qj0GPWn8AP2jx/kucqREAAAAASUVORK5CYII=
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
function print_palette() {
|
||||
L=$(( LOWER / 256))
|
||||
U=$(( (UPPER-1) / 256))
|
||||
for ((p = $L; p <= $U; p++))
|
||||
do
|
||||
echo "slot $((p*256))..$((p*256+255)):"
|
||||
echo -ne "\x1bP;1q"
|
||||
for i in {0..15}
|
||||
do
|
||||
a=$((i * 16 + p * 256))
|
||||
for j in {0..15}
|
||||
do
|
||||
echo -ne "#$((a+j))!6~"
|
||||
done
|
||||
echo -ne "\$-"
|
||||
done
|
||||
echo -e "\x1b\\"
|
||||
done
|
||||
}
|
||||
|
||||
colors=undefined
|
||||
max_colors=undefined
|
||||
|
||||
echo "Terminal Reports (XTSMGRAPHICS):"
|
||||
IFS=";" read -a REPLY -s -t 1 -d "S" -p $'\e[?1;1;0S'
|
||||
[[ ${REPLY[1]} == "0" ]] && colors=${REPLY[2]}
|
||||
echo "active colors: ${colors}"
|
||||
|
||||
IFS=";" read -a REPLY -s -t 1 -d "S" -p $'\e[?1;4;0S'
|
||||
[[ ${REPLY[1]} == "0" ]] && max_colors=${REPLY[2]}
|
||||
echo "max colors : ${max_colors}"
|
||||
echo
|
||||
|
||||
|
||||
# query up to colors by default
|
||||
# if colors is undefined (no XTSMGRAPHICS), assume 256
|
||||
ARG1=${1:-${colors}}
|
||||
if [[ $colors == "undefined" ]]
|
||||
then
|
||||
ARG1=${1:-256}
|
||||
fi
|
||||
LOWER=0
|
||||
UPPER=$ARG1
|
||||
ARG2=${2:-undefined}
|
||||
if [[ $ARG2 != "undefined" ]]
|
||||
then
|
||||
LOWER=ARG1
|
||||
UPPER=ARG2
|
||||
fi
|
||||
|
||||
if [[ $colors != "undefined" ]]
|
||||
then
|
||||
if [[ $colors -lt $UPPER ]] || [[ $colors -lt 256 ]]
|
||||
then
|
||||
echo -e "\x1b[33mNote: Active colors is smaller than test range."
|
||||
echo -e "A spec-conform terminal may repeat colors in 'slot mod ${colors}'.\x1b[m"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
echo -e "\x1b[33mNote: Cannot query active colors."
|
||||
echo -e "The terminal may repeat colors beyond it max slot (e.g. slot mod 16).\x1b[m"
|
||||
echo
|
||||
fi
|
||||
|
||||
print_palette
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
function smiling_smiley() {
|
||||
echo -ne '\x1bP;2q"1;1;60;60
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4]!22?!4]$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4~!22?!4~$-
|
||||
#6!60~$
|
||||
!15?#1!30N$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function indifferent_smiley() {
|
||||
echo -ne '\x1bP;2q"1;1;60;60
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4]!22?!4]$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!30N$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function sad_smiley() {
|
||||
echo -ne '\x1bP;2q"1;1;60;60
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4]!22?!4]$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!30N$
|
||||
!15?#1!4o!22?!4o$-
|
||||
#6!60~$
|
||||
!15?#1!4B!22?!4B$-
|
||||
#6!60~$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function smiling_smiley_slim() {
|
||||
echo -ne '\x1bP;1q"1;1;60;60
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
!15?#1!4~!22?!4~$-
|
||||
!15?#1!30N
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function indifferent_smiley_slim() {
|
||||
echo -ne '\x1bP;1q"1;1;60;60
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
!15?#6!4~!22?!4~$-
|
||||
!15?!4o!22?!4o$ !15?#1!30N$-
|
||||
!15?#6!4B!22?!4B
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function sad_smiley_slim() {
|
||||
echo -ne '\x1bP;1q"1;1;60;60
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
!15?#1!30N$
|
||||
!15?#1!4o!22?!4o$-
|
||||
!15?#1!4B!22?!4B$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function full() {
|
||||
smiling_smiley
|
||||
sleep .5
|
||||
indifferent_smiley
|
||||
sleep .5
|
||||
sad_smiley
|
||||
sleep .5
|
||||
indifferent_smiley
|
||||
sleep .5
|
||||
smiling_smiley
|
||||
}
|
||||
|
||||
function slim() {
|
||||
smiling_smiley
|
||||
sleep .5
|
||||
indifferent_smiley_slim
|
||||
sleep .5
|
||||
sad_smiley_slim
|
||||
sleep .5
|
||||
indifferent_smiley_slim
|
||||
sleep .5
|
||||
smiling_smiley_slim
|
||||
}
|
||||
|
||||
# clear screen and place cursor to 1;10
|
||||
echo -ne '\x1b[2J\x1b[10;1H'
|
||||
|
||||
# switch sixel scrolling off
|
||||
echo -ne '\x1b[?80h'
|
||||
|
||||
case "$1" in
|
||||
full ) full ;;
|
||||
slim ) slim ;;
|
||||
esac
|
||||
|
||||
# re-enable sixel scrolling
|
||||
echo -ne '\x1b[?80l'
|
||||
|
Before Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 6.3 MiB |
|
Before Width: | Height: | Size: 599 KiB |
|
Before Width: | Height: | Size: 988 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 324 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |