[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"xterm.js\",\n  \"image\": \"mcr.microsoft.com/devcontainers/typescript-node:22-bookworm\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/node:1\": {\n      \"version\": 22\n    } // yarn\n  },\n  \"forwardPorts\": [\n    3000\n  ],\n  \"postCreateCommand\": \"npm install && npm run setup\",\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        \"dbaeumer.vscode-eslint\",\n        \"editorconfig.editorconfig\",\n        \"hbenl.vscode-mocha-test-adapter\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{j,t}s]\nmax_line_length = 100\n\n[*.css]\nindent_size = 4\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a bug report\n---\n\n<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->\n<!-- Describe the bug here. -->\n\n## Details\n- Browser and browser version: \n- OS version: \n- xterm.js version: \n\n### Steps to reproduce\n\n1. \n2. \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: FAQ\n    url:  https://github.com/xtermjs/xterm.js/wiki/FAQ\n    about: See our frequently asked questions before filing a bug report\n  - name: Support / Q&A\n    url:  https://github.com/xtermjs/xterm.js/discussions/categories/q-a\n    about: Use GitHub Discussions for community support and general Q&A\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a feature or enhancement\n---\n\n<!-- ⚠️ Please search existing issues to avoid creating duplicates. ⚠️ -->\n<!-- Describe the enhancement here. -->\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# xterm.js Copilot Instructions\n\n## Architecture Overview\n\n**Core Structure**: xterm.js is a multi-target terminal emulator with three main distributions:\n- `src/browser/`: Full-featured browser terminal with DOM rendering\n- `src/headless/`: Server-side terminal for Node.js (no DOM)\n- `src/common/`: Shared core logic (parsing, buffer management, terminal state)\n\n**Key Classes**:\n- `Terminal` (browser/headless): Public API wrapper\n- `CoreTerminal` (common): Core terminal logic and state\n- `CoreBrowserTerminal` (browser): Browser-specific terminal implementation\n\n## Development Workflow\n\n**Build System**:\n```bash\nnpm run build && npm run esbuild # Build all TypeScript and bundle\n```\n\n**Testing**:\n- Unit tests: `npm run test-unit` (Mocha)\n- Unit tests filtering to file: `npm run test-unit -- **/fileName.ts\n- Per-addon unit tests: `npm run test-unit -- addons/addon-image/out-esbuild/*.test.js`\n- Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit)\n- Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work\n- Integration tests by addon: `npm run test-integration -- --suite=addon-search`. Suites always follow the format `addon-<something>`\n- Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them\n\n## Addon Development Pattern\n\nAll addons follow this structure:\n```typescript\nexport class MyAddon implements ITerminalAddon {\n  activate(terminal: Terminal): void {\n    // Called when loaded via terminal.loadAddon()\n    // Register handlers, access terminal APIs\n  }\n  dispose(): void {\n    // Cleanup when addon is disposed\n  }\n}\n```\n\n**Key Examples**:\n- `addons/addon-fit/`: Terminal sizing\n- `addons/addon-webgl/`: GPU-accelerated rendering\n- `addons/addon-search/`: Text search functionality\n\n## Project-Specific Conventions\n\n**TypeScript Project Structure**: Uses TypeScript project references (`tsconfig.all.json`) for incremental builds across browser/headless/addons.\n\n**API Design**: \n- Browser and headless terminals share the same public API\n- Proposed APIs require `allowProposedApi: true` option\n- Constructor-only options (cols, rows) cannot be changed after instantiation\n\n**Testing Utilities**: Use `TestUtils.ts` helpers:\n- `openTerminal(ctx, options)` for setup\n- `pollFor(page, fn, expectedValue)` for async assertions\n- `writeSync(page, data)` for terminal input\n\n## Common Patterns\n\n**Parser Integration**: Register custom escape sequence handlers:\n```typescript\nterminal.parser.registerCsiHandler('m', params => {\n  // Handle SGR sequences\n  return true; // Handled\n});\n```\n\n**Buffer Access**: Read terminal content via buffer API:\n```typescript\nconst line = terminal.buffer.active.getLine(0);\nconst cell = line?.getCell(0);\n```\n\n**Events**: All terminals emit standard events (onData, onResize, onRender) plus platform-specific ones.\n\n## Critical Implementation Details\n\n- Terminal rendering uses either DOM or WebGL renderers\n- Buffer lines are immutable; create new instances for modifications\n- Character width handling supports Unicode 11+ and grapheme clustering\n- Mouse events translate web events to terminal protocols (X10, VT200, etc.)\n- Color theming supports both palette and true color modes\n\n## Writing unit tests\n\n- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.\n"
  },
  {
    "path": ".github/hooks/setupRepo.json",
    "content": "{\n\t\"hooks\": {\n\t\t\"SessionStart\": [\n\t\t\t{\n\t\t\t\t\"type\": \"command\",\n\t\t\t\t\"command\": \"npm run agent:setup-repo\"\n\t\t\t}\n\t\t]\n\t}\n}"
  },
  {
    "path": ".github/instructions/benchmark.instructions.md",
    "content": "---\napplyTo: '**/*.benchmark.ts'\n---\n# Benchmark run instructions\n\n- Full suite: `npm run benchmark`\n- Single benchmark file:\n  - Tree: `npm run benchmark -- -t out-test/benchmark/Event.benchmark.js`\n  - Run file: `npm run benchmark -- -s \"out-test/benchmark/Event.benchmark.js\" out-test/benchmark/Event.benchmark.js`\n- Single context/case:\n  - Use `-t` to get the path, then:\n  - `npm run benchmark -- -s \"<path>\" out-test/benchmark/Event.benchmark.js`\n\nWhen writing instructions, use `RuntimeCase` to measure pure runtime in ms, use `ThroughputRuntimeCase` when measuring throughput in MB/s.\n\nNotes:\n- Benchmarks run from built JS in `out-test/benchmark/*.benchmark.js`.\n- Keep `NODE_PATH=./out` (handled by the npm script).\n"
  },
  {
    "path": ".github/instructions/unit-test.instructions.md",
    "content": "---\napplyTo: '**/*.test.ts'\n---\nWhen writing unit tests follow these rules:\n\n- When writing unit tests for addons, always create a real xterm.js instance instead of mocking it.\n- Prefer `assert.ok` over `assert.notStrictEqual` when checking something is undefined or not.\n- Avoid comments as most tests should be self-documenting.\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js 22.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: 22.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: npm ci\n    - name: Setup and run tsc\n      run: npm run setup\n    - name: Esbuild\n      run: npm run esbuild\n    - name: Zip artifacts\n      run: |\n        zip -r compressed-build \\\n          ./lib/* \\\n          ./out/* \\\n          ./out-*/* \\\n          ./addons/addon-attach/lib/* \\\n          ./addons/addon-attach/out/* \\\n          ./addons/addon-attach/out-*/* \\\n          ./addons/addon-clipboard/lib/* \\\n          ./addons/addon-clipboard/out/* \\\n          ./addons/addon-clipboard/out-*/* \\\n          ./addons/addon-fit/lib/* \\\n          ./addons/addon-fit/out/* \\\n          ./addons/addon-fit/out-*/* \\\n          ./addons/addon-image/lib/* \\\n          ./addons/addon-image/out/* \\\n          ./addons/addon-image/out-*/* \\\n          ./addons/addon-ligatures/lib/* \\\n          ./addons/addon-ligatures/out/* \\\n          ./addons/addon-ligatures/out-*/* \\\n          ./addons/addon-progress/lib/* \\\n          ./addons/addon-progress/out/* \\\n          ./addons/addon-progress/out-*/* \\\n          ./addons/addon-search/lib/* \\\n          ./addons/addon-search/out/* \\\n          ./addons/addon-search/out-*/* \\\n          ./addons/addon-serialize/lib/* \\\n          ./addons/addon-serialize/out/* \\\n          ./addons/addon-serialize/out-*/* \\\n          ./addons/addon-unicode11/lib/* \\\n          ./addons/addon-unicode11/out/* \\\n          ./addons/addon-unicode11/out-*/* \\\n          ./addons/addon-unicode-graphemes/lib/* \\\n          ./addons/addon-unicode-graphemes/out/* \\\n          ./addons/addon-unicode-graphemes/out-*/* \\\n          ./addons/addon-web-links/lib/* \\\n          ./addons/addon-web-links/out/* \\\n          ./addons/addon-web-links/out-*/* \\\n          ./addons/addon-web-fonts/lib/* \\\n          ./addons/addon-web-fonts/out/* \\\n          ./addons/addon-web-fonts/out-*/* \\\n          ./addons/addon-webgl/lib/* \\\n          ./addons/addon-webgl/out/* \\\n          ./addons/addon-webgl/out-*st/*\n    - name: Upload artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: build-artifacts\n        path: compressed-build.zip\n        if-no-files-found: error\n\n  lint:\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js 22.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: 22.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: |\n        npm ci\n    - name: Lint code\n      env:\n        NODE_OPTIONS: --max_old_space_size=4096\n      run: npm run lint\n    - name: Lint API\n      run: npm run lint-api\n\n  test-unit-coverage:\n    needs: build\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js 22.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: 22.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: |\n        npm ci\n    - uses: actions/download-artifact@v4\n      with:\n        name: build-artifacts\n    - name: Unzip artifacts\n      shell: bash\n      run: |\n        if [ \"$RUNNER_OS\" == \"Windows\" ]; then\n          pwsh -Command \"7z x compressed-build.zip -aoa -o${{ github.workspace }}\"\n        else\n          unzip -o compressed-build.zip\n        fi\n        ls -R\n    - name: Unit test coverage\n      run: |\n        npm run test-unit-coverage --forbid-only\n        EXIT_CODE=$?\n        ./node_modules/.bin/nyc report --reporter=cobertura\n        exit $EXIT_CODE\n\n  test-unit:\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        node-version: [22]\n        runs-on: [ubuntu, macos, windows]\n    runs-on: ${{ matrix.runs-on }}-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js ${{ matrix.node-version }}.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: |\n        npm ci\n    - name: Wait for build job\n      uses: NathanFirmo/wait-for-other-job@v1.1.1\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        job: build\n    - uses: actions/download-artifact@v4\n      with:\n        name: build-artifacts\n    - name: Unzip artifacts\n      shell: bash\n      run: |\n        if [ \"$RUNNER_OS\" == \"Windows\" ]; then\n          pwsh -Command \"7z x compressed-build.zip -aoa -o${{ github.workspace }}\"\n        else\n          unzip -o compressed-build.zip\n        fi\n        ls -R\n    - name: Unit tests\n      run: npm run test-unit --forbid-only\n\n  test-integration:\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        node-version: [22] # just one as integration tests are about testing in browser\n        runs-on: [ubuntu-22.04] # macos is flaky\n        browser: [chromium, firefox, webkit]\n    runs-on: ${{ matrix.runs-on }}\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js ${{ matrix.node-version }}.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: |\n        npm ci\n    - name: Install playwright\n      run: npx playwright install --with-deps ${{ matrix.browser }}\n    - name: Wait for build job\n      uses: NathanFirmo/wait-for-other-job@v1.1.1\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        job: build\n    - uses: actions/download-artifact@v4\n      with:\n        name: build-artifacts\n    - name: Unzip artifacts\n      shell: bash\n      run: |\n        if [ \"$RUNNER_OS\" == \"Windows\" ]; then\n          pwsh -Command \"7z x compressed-build.zip -aoa -o${{ github.workspace }}\"\n        else\n          unzip -o compressed-build.zip\n        fi\n        ls -R\n    - name: Build demo client\n      run: npm run esbuild-demo-client\n    - name: Build demo server\n      run: npm run esbuild-demo-server\n    - name: Integration tests (core) # Tests use 50% workers to reduce flakiness\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core\n    - name: Integration tests (addon-attach)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-attach\n    - name: Integration tests (addon-clipboard)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-clipboard\n    - name: Integration tests (addon-fit)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-fit\n    - name: Integration tests (addon-image)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-image\n    - name: Integration tests (addon-progress)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-progress\n    - name: Integration tests (addon-search)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-search\n    - name: Integration tests (addon-serialize)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-serialize\n    - name: Integration tests (addon-unicode-graphemes)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode-graphemes\n    - name: Integration tests (addon-unicode11)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode11\n    - name: Integration tests (addon-web-fonts)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-fonts\n    - name: Integration tests (addon-web-links)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-links\n    - name: Integration tests (addon-webgl)\n      run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl\n\n  release-dry-run:\n    needs: build\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [22]\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js ${{ matrix.node-version }}.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: |\n        npm ci\n    - name: Install playwright\n      run: npx playwright install\n    - uses: actions/download-artifact@v4\n      with:\n        name: build-artifacts\n    - name: Unzip artifacts\n      shell: bash\n      run: |\n        if [ \"$RUNNER_OS\" == \"Windows\" ]; then\n          pwsh -Command \"7z x compressed-build.zip -aoa -o${{ github.workspace }}\"\n        else\n          unzip -o compressed-build.zip\n        fi\n        ls -R\n    - name: Package headless\n      run: |\n        npm run package-headless\n        node ./bin/package_headless.js\n    - name: Publish to npm (dry run)\n      run: node ./bin/publish.js --dry\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n  schedule:\n    - cron: '41 17 * * 0'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v3\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v2\n      with:\n        languages: ${{ matrix.language }}\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v2\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": ".github/workflows/copilot-setup-steps.yml",
    "content": "name: \"Copilot Setup Steps\"\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - .github/workflows/copilot-setup-steps.yml\n  pull_request:\n    paths:\n      - .github/workflows/copilot-setup-steps.yml\n\njobs:\n  copilot-setup-steps:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@v3\n        with:\n          node-version: 22.x\n          cache: 'npm'\n      - name: Install dependencies\n        run: npm ci\n      - name: Setup and run tsc\n        run: npm run setup\n      - name: Esbuild\n        run: npm run esbuild\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    # If a commit reaches master, assume it has passed CI via PR and publish\n    # without running tests to save time to publish\n    branches: [ \"master\" ]\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Use Node.js 22.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: 22.x\n        cache: 'npm'\n    - name: Install dependencies\n      run: npm ci\n    - name: Build\n      run: npm run setup\n    - name: Package headless\n      run: |\n        npm run package-headless\n        node ./bin/package_headless.js\n    - name: Publish to npm\n      env:\n        NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n      run: node ./bin/publish.js\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\n*.swp\n.lock-wscript\nlib/\nout/\nout-demo/\nout-test/\nout-esbuild/\nout-esbuild-test/\n.nyc_output/\nMakefile.gyp\n*.Makefile\n*.target.gyp.mk\n*.node\nexample/*.log\ndocs/\nnpm-debug.log\n/.idea/\n.env\nbuild/\n.DS_Store\nyarn.lock\ntest-results/\n\n# Keep bundled code out of Git\ndist/\ndemo/dist/\n\n# dont commit benchmark folders\n.benchmark/\ntimeline/\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - css/\n!css/**/*.css\n\n# Whitelist - dist/\n!dist/**/*.js\n!dist/**/*.js.map\n\n!dist/**/*.css\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n\n# Blacklist - (normal behavior) these will override any whitelist\nheadless/\ntypings/xterm-headless.d.ts\n"
  },
  {
    "path": ".nvmrc",
    "content": "22\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    \"recommendations\": [\n        \"dbaeumer.vscode-eslint\"\n    ]\n}"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Unit Tests\",\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/mocha\",\n      \"windows\": {\n        \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/mocha.cmd\"\n      },\n      \"runtimeArgs\": [\n        \"--colors\",\n        \"--recursive\",\n        \"${workspaceRoot}/out/**/*.test.js\"\n      ],\n      \"env\": {\n        \"NODE_PATH\": \"${workspaceRoot}/out\"\n      },\n      \"sourceMaps\": true,\n      \"outFiles\": [ \"${workspaceRoot}/out/**/*.js\" ],\n      \"internalConsoleOptions\": \"openOnSessionStart\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Integration Tests\",\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/mocha\",\n      \"windows\": {\n        \"runtimeExecutable\": \"${workspaceRoot}/node_modules/.bin/mocha.cmd\"\n      },\n      \"runtimeArgs\": [\n        \"--colors\",\n        \"--recursive\",\n        \"${workspaceRoot}/out/**/*.api.js\"\n      ],\n      \"sourceMaps\": true,\n      \"outFiles\": [ \"${workspaceRoot}/lib/**/*.js\" ],\n      \"internalConsoleOptions\": \"openOnSessionStart\"\n    },\n    {\n      \"type\": \"chrome\",\n      \"request\": \"launch\",\n      \"name\": \"Demo Client (Chrome)\",\n      \"url\": \"http://127.0.0.1:3000\",\n      \"webRoot\": \"${workspaceFolder}/\"\n    },\n    {\n      \"type\": \"msedge\",\n      \"request\": \"launch\",\n      \"name\": \"Demo Client (Edge)\",\n      \"url\": \"http://127.0.0.1:3000\",\n      \"webRoot\": \"${workspaceFolder}/\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Demo Server\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"start\"],\n      \"stopOnEntry\": true,\n      \"runtimeVersion\": \"22\",\n      \"serverReadyAction\": {\n        \"action\": \"openExternally\",\n        \"pattern\": \"App listening to (http://.*?:[0-9]+)\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"typescript.preferences.importModuleSpecifier\": \"non-relative\",\n  \"typescript.preferences.quoteStyle\": \"single\"\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"presentation\": {\n    \"echo\": false,\n    \"reveal\": \"always\",\n    \"focus\": false,\n    \"panel\": \"dedicated\",\n    \"showReuseMessage\": true\n  },\n  \"tasks\": [\n    // Compound tasks\n    {\n      \"label\": \"dev\",\n      \"detail\": \"Runs all tasks required to run the demo in a single terminal using concurrently. This does not support problem matching.\",\n      \"type\": \"npm\",\n      \"script\": \"dev\",\n      \"isBackground\": true,\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      }\n    },\n    {\n      \"label\": \"dev (separate terminals)\",\n      \"detail\": \"Runs all tasks required to run the demo in separate terminals. This does support problem matching.\",\n      \"dependsOn\": [\"demo-server\", \"tsc\", \"esbuild\", \"esbuild-demo-client\", \"esbuild-demo-server\"],\n      \"group\": \"build\"\n    },\n\n    // Demo\n    {\n      \"label\": \"demo-server\",\n      \"type\": \"npm\",\n      \"script\": \"start\",\n      \"group\": \"build\",\n      \"isBackground\": true,\n      \"problemMatcher\": [],\n      \"presentation\": {\n        \"group\": \"xterm-demo\"\n      }\n    },\n\n    // Build\n    {\n      \"label\": \"tsc\",\n      \"type\": \"npm\",\n      \"script\": \"tsc-watch\",\n      \"group\": \"build\",\n      \"isBackground\": true,\n      \"problemMatcher\": \"$tsc-watch\",\n      \"presentation\": {\n        \"group\": \"xterm-build\"\n      }\n    },\n    {\n      \"label\": \"esbuild\",\n      \"type\": \"npm\",\n      \"script\": \"esbuild-watch\",\n      \"group\": \"build\",\n      \"isBackground\": true,\n      \"problemMatcher\": \"$esbuild-watch\",\n      \"presentation\": {\n        \"group\": \"xterm-build\"\n      }\n    },\n    {\n      \"label\": \"esbuild-demo-client\",\n      \"type\": \"npm\",\n      \"script\": \"esbuild-demo-client-watch\",\n      \"dependsOn\": [\"esbuild\", \"tsc\"],\n      \"group\": \"build\",\n      \"isBackground\": true,\n      \"problemMatcher\": \"$esbuild-watch\",\n      \"presentation\": {\n        \"group\": \"xterm-demo\"\n      }\n    },\n    {\n      \"label\": \"esbuild-demo-server\",\n      \"type\": \"npm\",\n      \"script\": \"esbuild-demo-server-watch\",\n      \"dependsOn\": [\"esbuild\", \"tsc\"],\n      \"group\": \"build\",\n      \"isBackground\": true,\n      \"problemMatcher\": \"$esbuild-watch\",\n      \"presentation\": {\n        \"group\": \"xterm-demo\"\n      }\n    },\n\n    // Test\n    {\n      \"type\": \"npm\",\n      \"script\": \"test\",\n      \"group\":{\n        \"kind\": \"test\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": []\n    }\n  ]\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\neducation, socio-economic status, nationality, personal appearance, race,\nreligion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting [@Tyriar](https://twitter.com/Tyriar) or [@pariskasid](https://twitter.com/pariskasid) on Twitter. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute to xterm.js\n\n- [Contributing code](#contributing-code)\n- [Opening issues](#opening-issues)\n- [Answering discussion questions](#answering-discussion-questions)\n\n## Contributing code\n\nYou can find issues to work on by looking at issues labeled with [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). It's a good idea to comment on the issue saying that you're looking into it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:\n\n- Fork [xterm.js](https://github.com/xtermjs/xterm.js/) ([how to fork a repo](https://help.github.com/articles/fork-a-repo)).\n- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running.\n- Make the fix and verify it works in the demo. Be sure to follow thew general code style of the rest of the project.\n- If your changes are easy to test or likely to regress in the future, add tests. These could be unit or integration (playwright) tests.\n- Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)).\n\n> ![TIP]\n> Don't put more than one feature or fix in a single pull request. The smaller pull requests are the easier they are to review and merge.\n\nBy contributing code to xterm.js you:\n\n - Agree to license the contributed code under xterm.js' [MIT license](LICENSE).\n - Confirm that you have the right to contribute and license the code in question. This means that 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.\n\n## Opening issues\n\nThe preferred way to report bugs or request features is to use\n[GitHub issues](http://github.com/xtermjs/xterm.js/issues).\n\n### Creating great issues\n\n- Include information about **the browser in which the problem occurred** or the terminal being used. If you tested several browsers and the problem occurred in all of them, mention this fact in the bug report. Also include browser version numbers and the operating system that you're on.\n- Include the version of xterm.js being used, preferably either with the latest `beta` tagged release on npm or reproducing in the demo on the `master` branch.\n- 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.\n- If the problem can not be reproduced in the [demo of xterm.js](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo), provide an HTML document that demonstrates the problem.\n- Be polite and follow [the code of conduct](https://github.com/xtermjs/xterm.js?tab=coc-ov-file#readme).\n\n### Issue triaging philosophy\n\nIt's pretty common for maintainers of large open source projects to suffer from burnout, especially when needing to triage a large number of incoming issues instead of actually building things. Here are some of the steps we take to try mitigate this:\n\n- Support questions live in [GH discussions](https://github.com/xtermjs/xterm.js/discussions), issues may be transfered there without further comment and core maintainers may or may not participate in discussions.\n- Issues are strictly for well defined features or bugs that are _actionable_.\n- Sometimes features are out of scope. A common example of this is a niche feature that the pricipal implementation ([VS Code](https://code.visualstudio.com/)) won't leverage and therefore would be difficult to maintain and likely suffer from bitrot. The reporter may not agree with this, but you could always create an addon if that works or maintain your own fork if it comes to that.\n- If a feature does not have a clear way forward or needs more discussion it may be closed ro moved to a discussion.\n- If a bug is not easily reproducible it may be closed or moved to a discussion. Generally issues that are labeled are something we want to do or has actionable steps to look into further.\n\n## Answering discussion questions\n\nIssues 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."
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)\nCopyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)\nCopyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# [![xterm.js](images/logo-full.png)](https://xtermjs.org)\n\nXterm.js is a frontend component that enables applications to bring fully-featured terminals to their users in the browser. It's used by popular projects such as [VS Code](https://code.visualstudio.com/) (and its forks), [Tabby](https://tabby.sh/) and [Hyper](https://hyper.is/).\n\n## Features\n\n- **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.\n- **Performant**: Xterm.js is *really* fast and includes an optional GPU-accelerated renderer.\n- **Rich Unicode support**: Supports CJK, emojis, and IMEs.\n- **Self-contained**: The core library has zero dependencies.\n- **Accessible**: Screen reader mode and minimum contrast ratio support can be turned on.\n- **And much more**: Links, theming, custom glyphs, addons, well documented API, etc.\n\n## What xterm.js is not\n\n- Xterm.js is not a terminal application that you can download and use on your computer.\n- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output) through a library like [node-pty](https://github.com/microsoft/node-pty).\n\n## Getting Started\n\nFirst, you need to install the module. We ship exclusively through [npm](https://www.npmjs.com), so you need that installed and then add [@xterm/xterm](https://www.npmjs.com/package/@xterm/xterm) as a dependency by running:\n\n```bash\nnpm install --save @xterm/xterm\n```\n\nThe recommended way to load xterm.js with the ES module syntax, either using TypeScript or a modern JS tooling. Note that both CommonJS and ES module files are shipped with the npm package.\n\n```javascript\nimport { Terminal } from '@xterm/xterm';\n```\n\nThen instantiate a `Terminal` object, open it on an element and write to it:\n\n```javascript\nconst term = new Terminal();\nterm.open(document.getElementById('terminal'));\nterm.write('Hello from \\x1B[1;3;31mxterm.js\\x1B[0m $ ')\n```\n\nMost use cases will hook up input and output to a pseudoterminal API such as [node-pty](https://www.npmjs.com/package/node-pty) which will drive the terminal.\n\n```javascript\npty.onData(data => term.write(data));\nterm.onData(data => pty.write(data));\n```\n\nThen make sure to also include the `css/xterm.css` file, for example in HTML:\n\n```html\n<link rel=\"stylesheet\" href=\"node_modules/@xterm/xterm/css/xterm.css\" />\n```\n\n### Standalone example\n\nHere is a complete standalone example in a HTML file:\n\n```html\n<!doctype html>\n  <html>\n    <head>\n      <link rel=\"stylesheet\" href=\"node_modules/@xterm/xterm/css/xterm.css\" />\n      <script src=\"node_modules/@xterm/xterm/lib/xterm.js\"></script>\n    </head>\n    <body>\n      <div id=\"terminal\"></div>\n      <script>\n        const term = new Terminal();\n        term.open(document.getElementById('terminal'));\n        term.write('Hello from \\x1B[1;3;31mxterm.js\\x1B[0m $ ')\n      </script>\n    </body>\n  </html>\n```\n\n### Addons\n\nAddons 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:\n\n```bash\nnpm install --save @xterm/addon-web-links\n```\n\nThen import the addon, instantiate it and call `Terminal.loadAddon`:\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { WebLinksAddon } from '@xterm/addon-web-links';\n\nconst terminal = new Terminal();\n// Load WebLinksAddon on terminal, this is all that's needed to get web links\n// working in the terminal.\nterminal.loadAddon(new WebLinksAddon());\n```\n\nThe xterm.js team maintains the following addons, but anyone can build them:\n\n- [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket\n- [`@xterm/addon-clipboard`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard): Access the browser's clipboard\n- [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element\n- [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support\n- [`@xterm/addon-ligatures`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-ligatures): Enables rendering of ligatures\n- [`@xterm/addon-progress`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-progress): Adds support for the progress API (`OSC 9;4`)\n- [`@xterm/addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search): Adds search functionality\n- [`@xterm/addon-serialize`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize): Serializes the terminal's buffer to VT sequences or HTML\n- [`@xterm/addon-unicode-graphemes`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode-graphemes): Enhanced unicode support including grapheme clustering (experimental)\n- [`@xterm/addon-unicode11`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11): Updates character widths to their unicode11 values\n- [`@xterm/addon-web-fonts`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-fonts): Easily integrate web fonts\n- [`@xterm/addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links): Adds web link detection and interaction\n- [`@xterm/addon-webgl`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl): Renders xterm.js using a `canvas` element's webgl2 context\n\n## Browser Support\n\nSince xterm.js is typically implemented as a developer tool, generally only modern evergreen browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*. Xterm.js also 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.\n\n### Node.js Support\n\nWe also publish [`@xterm/headless`](https://www.npmjs.com/package/@xterm/headless) which is a stripped down version of xterm.js that runs headless 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](https://www.npmjs.com/package/@xterm/addon-serialize) so it can get all state restored upon reconnection.\n\n## API\n\nThe 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.\n\nSome APIs may be marked with *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.\n\n## Releases\n\nStable releases are done on an as needed basis. All current and past releases are available on this repo's [releases page](https://github.com/sourcelair/xterm.js/releases), you can see what's planned for upcoming releases looking through the repository [milestones](https://github.com/sourcelair/xterm.js/milestones).\n\n### Beta builds\n\nBeta releases are continuously published off the `master` branch. Install the latest beta build with:\n\n```bash\nnpm install --save @xterm/xterm@beta\n```\n\nThe principal implementation (VS Code) typically uses the latest or near the latest beta build. Generally they are quite stable but can potentially contain bugs or breaking changes. If stability is very important we recommend using the beta build primarily to test out new features and to verify bug fixes, unless you're tracking what's landing and are comfortable taking that risk.\n\n## Contributing\n\nRead [CONTRIBUTING.md](https://github.com/xtermjs/xterm.js/blob/master/CONTRIBUTING.md) to learn how to contribute to the project.\n\n## Real-world uses\n\nXterm.js is used in many world-class applications to provide great terminal experiences.\n\n- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js.\n- [**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.\n- [**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.\n- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.\n- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.\n- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by xterm.js.\n- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using xterm.js, socket.io, and ssh2.\n- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.\n- [**Cloud Commander**](https://cloudcmd.io \"Cloud Commander\"): Orthodox web file manager with console and editor.\n- [**Next Tech**](https://next.tech \"Next Tech\"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.\n- [**RStudio**](https://www.rstudio.com/products/RStudio \"RStudio\"): RStudio is an integrated development environment (IDE) for R.\n- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.\n- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy, and run in the cloud.\n- [**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.\n- [**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.\n- [**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.\n- [**Portainer**](https://portainer.io): Simple management UI for Docker.\n- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising xterm.js, SJCL & websockets.\n- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.\n- [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript.\n- [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations.\n- [**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.\n- [**Script Runner**](https://github.com/ioquatix/script-runner): Run scripts (or a shell) in Atom.\n- [**Whack Whack Terminal**](https://github.com/Microsoft/WhackWhackTerminal): Terminal emulator for Visual Studio 2017.\n- [**VTerm**](https://github.com/vterm/vterm): Extensible terminal emulator based on Electron and React.\n- [**electerm**](http://electerm.html5beta.com): electerm is a terminal/ssh/sftp client(mac, win, linux) based on electron/node-pty/xterm.\n- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.\n- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.\n- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.\n- [**rtty**](https://github.com/zhaojh329/rtty): Access your terminals from anywhere via the web.\n- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS.\n- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.\n- [**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.\n- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users.\n- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.\n- [**Hyper**](https://hyper.is): A terminal built on web technologies.\n- [**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.\n- [**GoTTY**](https://github.com/sorenisanerd/gotty): A simple command line tool that shares your terminal as a web application based on xterm.js.\n- [**genact**](https://github.com/svenstaro/genact): A nonsense activity generator.\n- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.\n- [**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.\n- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.\n- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.\n- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.\n- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.\n- [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation.\n- [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD\n- [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access.\n- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems.\n- [**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.\n- [**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.\n- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.\n- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.\n- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal\n- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js\n- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.\n- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.\n- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.\n- [**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, ...\n- [**Dank Domain**](https://www.DDgame.us/): Open source multiuser medieval game supporting old & new terminal emulation.\n- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio\n- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.\n- [**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\n- [**Replit**](https://replit.com): Collaborative browser based IDE with support for 50+ different languages.\n- [**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.\n- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages are supported, with results displayed by xterm.js.\n- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services.\n- [**Commas**](https://github.com/CyanSalt/commas): Commas is a hackable terminal and command runner.\n- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.\n- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH.\n- [**gifcast**](https://dstein64.github.io/gifcast/): Converts an asciinema cast to an animated GIF.\n- [**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.\n- [**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.\n- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech).\n- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. Discover more at [tessapp.dev](https://tessapp.dev)\n- [**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.\n- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption\n- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)\n- [**goormIDE**](https://ide.goorm.io/): Run almost every programming languages with real-time collaboration, live pair programming, and built-in messenger.\n- [**FleetDeck**](https://fleetdeck.io): Remote desktop & virtual terminal\n- [**OpenSumi**](https://github.com/opensumi/core): A framework helps you quickly build Cloud or Desktop IDE products.\n- [**KubeSail**](https://kubesail.com): The Self-Hosting Company - uses xterm to allow users to exec into kubernetes pods and build github apps\n- [**WiTTY**](https://github.com/syssecfsu/witty): Web-based interactive terminal emulator that allows users to easily record, share, and replay console sessions.\n- [**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.\n- [**hack.courses**](https://hack.courses): Interactive Linux and command-line classes using xterm.js to expose a real terminal available for everyone.\n- [**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.\n- [**CloudTTY**](https://github.com/cloudtty/cloudtty): A Friendly Kubernetes CloudShell (Web Terminal).\n- [**Go SSH Web Client**](https://github.com/wuchihsu/go-ssh-web-client): A simple SSH web client using Go, WebSocket and Xterm.js.\n- [**web3os**](https://web3os.sh): A decentralized operating system for the next web\n- [**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.\n- [**Super Terminal**](https://github.com/bugwheels94/super-terminal): It is a http based terminal for developers who dont like repetition and save time.\n- [**graSSHopper**](https://grasshopper.coding.kiwi): A simple SSH client with file explorer, history and many more features.\n- [**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.\n- [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab.\n- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.\n- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments\n- [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows.\n- [**eva**](https://github.com/info24/eva): Eva is a web application for SSH remote login, developed in Go.\n- [**OpenSFTP**](https://opensftp.com): Super beautiful SSH and SFTP integrated workspace client.\n- [**balena**](https://www.balena.io/): Balena is a full-stack solution for developing, deploying, updating, and troubleshooting IoT Edge devices. We use xterm.js to manage & debug devices on [balenaCloud](https://www.balena.io/cloud).\n- [**Filet Cloud**](https://github.com/fuglaro/filet-cloud): The lean and powerful personal cloud ⛅.\n- [**pyTermTk**](https://github.com/ceccopierangiolieugenio/pyTermTk): Python Terminal Toolkit - a Spiced Up Cross Compatible TUI Library 🌶️, use xterm.js for the [HTML5 exporter](https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html).\n- [**ecmaOS**](https://ecmaos.sh): A kernel and suite of applications tying modern web technologies into a browser-based operating system.\n- [**LabEx**](https://labex.io): Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach.\n- [**EmuDevz**](https://afska.github.io/emudevz): A free coding game where players learn how to build an emulator from scratch.\n- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.\n- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.\n- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)\n\nDo 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. Please add any new contributions to the end of the list.\n\n## License Agreement\n\nIf 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.\n\nCopyright (c) 2017-2026, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>\nCopyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>\nCopyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n"
  },
  {
    "path": "addons/addon-attach/.gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": "addons/addon-attach/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-attach/LICENSE",
    "content": "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-attach/README.md",
    "content": "## @xterm/addon-attach\n\nAn addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables attaching to a web socket. This addon requires xterm.js v4+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-attach\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { AttachAddon } from '@xterm/addon-attach';\n\nconst terminal = new Terminal();\nconst attachAddon = new AttachAddon(webSocket);\nterminal.loadAddon(attachAddon);\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-attach/typings/addon-attach.d.ts) for more advanced usage.\n"
  },
  {
    "path": "addons/addon-attach/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-attach\",\n  \"version\": \"0.12.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-attach.js\",\n  \"module\": \"lib/addon-attach.mjs\",\n  \"types\": \"typings/addon-attach.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-attach/src/AttachAddon.ts",
    "content": "/**\n * Copyright (c) 2014, 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Implements the attach method, that attaches the terminal to a WebSocket stream.\n */\n\nimport type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';\nimport type { AttachAddon as IAttachApi } from '@xterm/addon-attach';\n\ninterface IAttachOptions {\n  bidirectional?: boolean;\n}\n\nexport class AttachAddon implements ITerminalAddon, IAttachApi {\n  private _socket: WebSocket;\n  private _bidirectional: boolean;\n  private _disposables: IDisposable[] = [];\n\n  constructor(socket: WebSocket, options?: IAttachOptions) {\n    this._socket = socket;\n    // always set binary type to arraybuffer, we do not handle blobs\n    this._socket.binaryType = 'arraybuffer';\n    this._bidirectional = !(options && options.bidirectional === false);\n  }\n\n  public activate(terminal: Terminal): void {\n    this._disposables.push(\n      addSocketListener(this._socket, 'message', ev => {\n        const data: ArrayBuffer | string = ev.data;\n        terminal.write(typeof data === 'string' ? data : new Uint8Array(data));\n      })\n    );\n\n    if (this._bidirectional) {\n      this._disposables.push(terminal.onData(data => this._sendData(data)));\n      this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));\n    }\n\n    this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));\n    this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));\n  }\n\n  public dispose(): void {\n    for (const d of this._disposables) {\n      d.dispose();\n    }\n  }\n\n  private _sendData(data: string): void {\n    if (!this._checkOpenSocket()) {\n      return;\n    }\n    this._socket.send(data);\n  }\n\n  private _sendBinary(data: string): void {\n    if (!this._checkOpenSocket()) {\n      return;\n    }\n    const buffer = new Uint8Array(data.length);\n    for (let i = 0; i < data.length; ++i) {\n      buffer[i] = data.charCodeAt(i) & 255;\n    }\n    this._socket.send(buffer);\n  }\n\n  private _checkOpenSocket(): boolean {\n    switch (this._socket.readyState) {\n      case WebSocket.OPEN:\n        return true;\n      case WebSocket.CONNECTING:\n        throw new Error('Attach addon was loaded before socket was open');\n      case WebSocket.CLOSING:\n        console.warn('Attach addon socket is closing');\n        return false;\n      case WebSocket.CLOSED:\n        throw new Error('Attach addon socket is closed');\n      default:\n        throw new Error('Unexpected socket state');\n    }\n  }\n}\n\nfunction addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {\n  socket.addEventListener(type, handler);\n  return {\n    dispose: () => {\n      if (!handler) {\n        // Already disposed\n        return;\n      }\n      socket.removeEventListener(type, handler);\n    }\n  };\n}\n"
  },
  {
    "path": "addons/addon-attach/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"@xterm/addon-attach\": [\n        \"../typings/addon-attach.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/addon-attach/test/AttachAddon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport WebSocket = require('ws');\n\nimport test from '@playwright/test';\nimport { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('Search Tests', () => {\n\n  test.beforeEach(async () => {\n    await ctx.proxy.reset();\n  });\n\n  test('string', async function(): Promise<any> {\n    const port = 8080;\n    const server = new WebSocket.Server({ port });\n    server.on('connection', socket => socket.send('foo'));\n    await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');\n    server.close();\n  });\n\n  test('utf8', async function(): Promise<any> {\n    const port = 8080;\n    const server = new WebSocket.Server({ port });\n    const data = new Uint8Array([102, 111, 111]);\n    server.on('connection', socket => socket.send(data));\n    await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');\n    server.close();\n  });\n});\n"
  },
  {
    "path": "addons/addon-attach/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-attach/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-attach/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-attach/typings/addon-attach.d.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-attach' {\n  export interface IAttachOptions {\n    /**\n     * Whether input should be written to the backend. Defaults to `true`.\n     */\n    bidirectional?: boolean;\n  }\n\n  export class AttachAddon implements ITerminalAddon {\n    constructor(socket: WebSocket, options?: IAttachOptions);\n    public activate(terminal: Terminal): void;\n    public dispose(): void;\n  }\n}\n"
  },
  {
    "path": "addons/addon-attach/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'AttachAddon';\nconst mainFile = 'addon-attach.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-clipboard/.gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": "addons/addon-clipboard/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-clipboard/LICENSE",
    "content": "Copyright (c) 2023, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-clipboard/README.md",
    "content": "## @xterm/addon-clipboard\n\nAn addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables\naccessing the system clipboard. This addon requires xterm.js v4+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-clipboard\n```\n\n### Usage\n\n```ts\nimport { Terminal } from 'xterm';\nimport { ClipboardAddon } from '@xterm/addon-clipboard';\n\nconst terminal = new Terminal();\nconst clipboardAddon = new ClipboardAddon();\nterminal.loadAddon(clipboardAddon);\n```\n\nTo use a custom clipboard provider\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { ClipboardAddon, IClipboardProvider, ClipboardSelectionType } from '@xterm/addon-clipboard';\n\nfunction b64Encode(data: string): string {\n  // Base64 encode impl\n}\n\nfunction b64Decode(data: string): string {\n  // Base64 decode impl\n}\n\nclass MyCustomClipboardProvider implements IClipboardProvider {\n  private _data: string\n  public readText(selection: ClipboardSelectionType): Promise<string> {\n    return Promise.resolve(b64Encode(this._data));\n  }\n  public writeText(selection: ClipboardSelectionType, data: string): Promise<void> {\n    this._data = b64Decode(data);\n    return Promise.resolve();\n  }\n}\n\nconst terminal = new Terminal();\nconst clipboardAddon = new ClipboardAddon(new MyCustomClipboardProvider());\nterminal.loadAddon(clipboardAddon);\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-clipboard/typings/addon-clipboard.d.ts) for more advanced usage.\n"
  },
  {
    "path": "addons/addon-clipboard/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-clipboard\",\n  \"version\": \"0.2.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-clipboard.js\",\n  \"module\": \"lib/addon-clipboard.mjs\",\n  \"types\": \"typings/addon-clipboard.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  },\n  \"dependencies\": {\n    \"js-base64\": \"^3.7.5\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-clipboard/src/ClipboardAddon.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm';\nimport { type IClipboardProvider, ClipboardSelectionType, type IBase64 } from '@xterm/addon-clipboard';\nimport { Base64 as JSBase64 } from 'js-base64';\n\nexport class ClipboardAddon implements ITerminalAddon {\n  private _terminal?: Terminal;\n  private _disposable?: IDisposable;\n\n  constructor(\n    private _base64: IBase64 = new Base64(),\n    private _provider: IClipboardProvider = new BrowserClipboardProvider()\n  ) {}\n\n  public activate(terminal: Terminal): void {\n    this._terminal = terminal;\n    this._disposable = terminal.parser.registerOscHandler(52, data => this._setOrReportClipboard(data));\n  }\n\n  public dispose(): void {\n    return this._disposable?.dispose();\n  }\n\n  private _readText(sel: ClipboardSelectionType, data: string): void {\n    const b64 = this._base64.encodeText(data);\n    this._terminal?.input(`\\x1b]52;${sel};${b64}\\x07`, false);\n  }\n\n  private _setOrReportClipboard(data: string): boolean | Promise<boolean> {\n    const args = data.split(';');\n    if (args.length < 2) {\n      return true;\n    }\n\n    const pc = args[0] as ClipboardSelectionType;\n    const pd = args[1];\n    if (pd === '?') {\n      const text = this._provider.readText(pc);\n\n      // Report clipboard\n      if (text instanceof Promise) {\n        return text.then((data) => {\n          this._readText(pc, data);\n          return true;\n        });\n      }\n\n      this._readText(pc, text);\n      return true;\n    }\n\n    // Clear clipboard if text is not a base64 encoded string.\n    let text = '';\n    try {\n      text = this._base64.decodeText(pd);\n    } catch {}\n\n\n    const result = this._provider.writeText(pc, text);\n    if (result instanceof Promise) {\n      return result.then(() => true);\n    }\n\n    return true;\n  }\n}\n\nexport class BrowserClipboardProvider implements IClipboardProvider {\n  public async readText(selection: ClipboardSelectionType): Promise<string> {\n    if (selection !== 'c') {\n      return Promise.resolve('');\n    }\n    return navigator.clipboard.readText();\n  }\n\n  public async writeText(selection: ClipboardSelectionType, text: string): Promise<void> {\n    if (selection !== 'c') {\n      return Promise.resolve();\n    }\n    return navigator.clipboard.writeText(text);\n  }\n}\n\nexport class Base64 implements IBase64 {\n  public encodeText(data: string): string {\n    return JSBase64.encode(data);\n  }\n  public decodeText(data: string): string {\n    const text = JSBase64.decode(data);\n    if (!JSBase64.isValid(data) || JSBase64.encode(text) !== data) {\n      return '';\n    }\n    return text;\n  }\n}\n"
  },
  {
    "path": "addons/addon-clipboard/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"@xterm/addon-clipboard\": [\n        \"../typings/addon-clipboard.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/browser\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-clipboard/test/ClipboardAddon.test.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepEqual, ok, strictEqual } from 'assert';\nimport { ITestContext, createTestContext, launchBrowser, openTerminal, timeout } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }, testInfo) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => {\n  await ctx.page.close();\n});\n\ntest.describe('ClipboardAddon', () => {\n\n  test.beforeEach(async ({}, testInfo) => {\n    // DEBT: This test doesn't work since the migration to @playwright/test\n    if (ctx.browser.browserType().name() !== 'chromium') {\n      testInfo.skip();\n      return;\n    }\n    if (ctx.browser.browserType().name() === 'chromium') {\n      // Enable clipboard access in chromium without user gesture\n      await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);\n    }\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.clipboard?.dispose();\n      window.clipboard = new ClipboardAddon();\n      window.term.loadAddon(window.clipboard);\n    `);\n  });\n\n  test.beforeEach(async () => {\n    await ctx.proxy.reset();\n  });\n\n  const testDataEncoded = 'aGVsbG8gd29ybGQ=';\n  const testDataDecoded = 'hello world';\n\n  test.describe('write data', async function (): Promise<any> {\n    test('simple string', async () => {\n      await ctx.proxy.write(`\\x1b]52;c;${testDataEncoded}\\x07`);\n      deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);\n    });\n    test('invalid base64 string', async () => {\n      await ctx.proxy.write(`\\x1b]52;c;${testDataEncoded}invalid\\x07`);\n      deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');\n    });\n    test('empty string', async () => {\n      await ctx.proxy.write(`\\x1b]52;c;${testDataEncoded}\\x07`);\n      await ctx.proxy.write(`\\x1b]52;c;\\x07`);\n      deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');\n    });\n  });\n\n  test.describe('read data', async function (): Promise<any> {\n    test('simple string', async () => {\n      await ctx.page.evaluate(`\n        window.data = [];\n        window.term.onData(e => data.push(e));\n      `);\n      await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world'));\n      await ctx.proxy.write(`\\x1b]52;c;?\\x07`);\n      deepEqual(await ctx.page.evaluate('window.data'), [`\\x1b]52;c;${testDataEncoded}\\x07`]);\n    });\n    test('clear clipboard', async () => {\n      await ctx.proxy.write(`\\x1b]52;c;!\\x07`);\n      await ctx.proxy.write(`\\x1b]52;c;?\\x07`);\n      deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');\n    });\n  });\n});\n"
  },
  {
    "path": "addons/addon-clipboard/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-clipboard/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-clipboard/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-clipboard/typings/addon-clipboard.d.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-clipboard' {\n  /**\n   * An xterm.js addon that enables accessing the system clipboard from\n   * xterm.js.\n   */\n  export class ClipboardAddon implements ITerminalAddon {\n    /**\n     * Creates a new clipboard addon.\n     * @param base64 An optional base64 encoder/decoder.\n     * @param provider An optional clipboard provider.\n     */\n    constructor(base64?: IBase64, provider?: IClipboardProvider);\n\n    /**\n     * Activates the addon\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void\n  }\n\n  /**\n   * Clipboard selection type. This is used to specify which selection buffer to\n   * read or write to.\n   * - SYSTEM `c`: The system clipboard.\n   * - PRIMARY `p`: The primary clipboard. This is provided for compatibility\n   *  with Linux X11.\n   */\n  export const enum ClipboardSelectionType {\n    SYSTEM = 'c',\n    PRIMARY = 'p',\n  }\n\n  export interface IBase64 {\n    /**\n    * Converts a utf-8 string to a base64 string.\n    * @param data The utf-8 string to convert to base64 string.\n    */\n    encodeText(data: string): string;\n\n    /**\n    * Converts a base64 string to a utf-8 string.\n    * @param data The base64 string to convert to utf-8 string.\n    * @throws An error if the input is not valid base64.\n    */\n    decodeText(data: string): string;\n  }\n\n  /**\n   * A default Base64 encoding and decoding type.\n   **/\n  export class Base64 implements IBase64 {\n    /**\n     * Converts a utf-8 string to a base64 string.\n     * @param data The utf-8 string to convert to base64 string.\n     */\n    public encodeText(data: string): string;\n\n    /**\n     * Converts a base64 string to a utf-8 string.\n     * @param data The base64 string to convert to utf-8 string.\n     * @throws An error if the input is not valid base64.\n     */\n    public decodeText(data: string): string;\n  }\n\n  export interface IClipboardProvider {\n    /**\n     * Gets the clipboard content.\n     * @param selection The clipboard selection to read.\n     * @returns A promise that resolves with clipboard selection data.\n     */\n    readText(selection: ClipboardSelectionType): string | Promise<string>;\n\n    /**\n     * Sets the clipboard content.\n     * @param selection The clipboard selection to set.\n     * @param data The clipboard text to write.\n     */\n    writeText(selection: ClipboardSelectionType, text: string): void | Promise<void>;\n  }\n\n  /**\n   * The clipboard provider interface that enables xterm.js to access the system clipboard.\n   */\n  export class BrowserClipboardProvider implements IClipboardProvider{\n    /**\n     * Reads text from the clipboard.\n     * @param selection The selection type to read from.\n     * @returns A promise that resolves with the text from the clipboard.\n     */\n    public readText(selection: ClipboardSelectionType): Promise<string>;\n\n    /**\n     * Writes text to the clipboard.\n     * @param selection The selection type to write to.\n     * @param data The text to write to the clipboard.\n     * @returns A promise that resolves when the text has been written to the clipboard.\n     */\n    public writeText(selection: ClipboardSelectionType, data: string): Promise<void>;\n  }\n}\n"
  },
  {
    "path": "addons/addon-clipboard/webpack.config.js",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'ClipboardAddon';\nconst mainFile = 'addon-clipboard.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd'\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-fit/.gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": "addons/addon-fit/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-fit/LICENSE",
    "content": "Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-fit/README.md",
    "content": "## @xterm/addon-fit\n\nAn 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+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-fit\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { FitAddon } from '@xterm/addon-fit';\n\nconst terminal = new Terminal();\nconst fitAddon = new FitAddon();\nterminal.loadAddon(fitAddon);\nterminal.open(containerElement);\nfitAddon.fit();\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-fit/typings/addon-fit.d.ts) for more advanced usage.\n"
  },
  {
    "path": "addons/addon-fit/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-fit\",\n  \"version\": \"0.11.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-fit.js\",\n  \"module\": \"lib/addon-fit.mjs\",\n  \"types\": \"typings/addon-fit.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-fit/src/FitAddon.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';\nimport type { FitAddon as IFitApi } from '@xterm/addon-fit';\nimport { ViewportConstants } from 'browser/shared/Constants';\n\ninterface ITerminalDimensions {\n  /**\n   * The number of rows in the terminal.\n   */\n  rows: number;\n\n  /**\n   * The number of columns in the terminal.\n   */\n  cols: number;\n}\n\nconst MINIMUM_COLS = 2;\nconst MINIMUM_ROWS = 1;\n\nfunction getWindow(e: Node): Window {\n  if (e?.ownerDocument?.defaultView) {\n    return e.ownerDocument.defaultView;\n  }\n\n  return window;\n}\nfunction _getComputedStyle(el: HTMLElement): CSSStyleDeclaration {\n  return getWindow(el).getComputedStyle(el, null);\n}\n\nexport class FitAddon implements ITerminalAddon, IFitApi {\n  private _terminal: Terminal | undefined;\n\n  public activate(terminal: Terminal): void {\n    this._terminal = terminal;\n  }\n\n  public dispose(): void {}\n\n  public fit(): void {\n    const dims = this.proposeDimensions();\n    if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {\n      return;\n    }\n\n    // Force a full render\n    if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {\n      this._terminal.resize(dims.cols, dims.rows);\n    }\n  }\n\n  public proposeDimensions(): ITerminalDimensions | undefined {\n    if (!this._terminal) {\n      return undefined;\n    }\n\n    if (!this._terminal.element || !this._terminal.element.parentElement) {\n      return undefined;\n    }\n\n    const dims: IRenderDimensions | undefined = this._terminal.dimensions;\n\n    if (!dims || dims.css.cell.width === 0 || dims.css.cell.height === 0) {\n      return undefined;\n    }\n\n    const showScrollbar = this._terminal.options.scrollbar?.showScrollbar ?? true;\n    const scrollbarWidth = (this._terminal.options.scrollback === 0 || !showScrollbar\n      ? 0\n      : (this._terminal.options.scrollbar?.width ?? ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));\n\n    const parentElementStyle = _getComputedStyle(this._terminal.element.parentElement);\n    const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n    const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n    const elementStyle = _getComputedStyle(this._terminal.element);\n    const elementPadding = {\n      top: parseInt(elementStyle.getPropertyValue('padding-top')),\n      bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n      right: parseInt(elementStyle.getPropertyValue('padding-right')),\n      left: parseInt(elementStyle.getPropertyValue('padding-left'))\n    };\n    const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n    const elementPaddingHor = elementPadding.right + elementPadding.left;\n    const availableHeight = parentElementHeight - elementPaddingVer;\n    const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;\n    const geometry = {\n      cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),\n      rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))\n    };\n    return geometry;\n  }\n}\n"
  },
  {
    "path": "addons/addon-fit/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"@xterm/addon-fit\": [\n        \"../typings/addon-fit.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/browser\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-fit/test/FitAddon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepEqual, ok, strictEqual } from 'assert';\nimport { ITestContext, createTestContext, openTerminal, timeout } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  ctx.page.setViewportSize({ width: 1024, height: 768 });\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('FitAddon', () => {\n  test.beforeEach(async function(): Promise<any> {\n    await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.fit?.dispose();\n      window.fit = new FitAddon();\n      window.term.loadAddon(window.fit);\n    `);\n  });\n\n  // test.beforeEach(async function(): Promise<any> {\n  //   await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);\n  //   await openTerminal(page);\n  // });\n\n  // after(async () => {\n  //   await browser.close();\n  // });\n\n  // afterEach(async function(): Promise<any> {\n  //   await ctx.proxy.dispose();\n  // });\n\n  test('no terminal', async function(): Promise<any> {\n    await ctx.page.evaluate(`window.fit2 = new FitAddon();`);\n    strictEqual(await ctx.page.evaluate(`window.fit2.proposeDimensions()`), undefined);\n    await ctx.page.evaluate(`window.fit2.dispose();`);\n  });\n\n  test.describe('proposeDimensions', () => {\n    test('default', async function(): Promise<any> {\n      await setDimensions();\n      const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`);\n      ok(dimensions.cols > 85);\n      ok(dimensions.cols < 88);\n      ok(dimensions.rows > 24);\n      ok(dimensions.rows < 29);\n    });\n\n    test('width', async function(): Promise<any> {\n      await setDimensions(1008);\n      const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`);\n      ok(dimensions.cols > 108);\n      ok(dimensions.cols < 111);\n      ok(dimensions.rows > 24);\n      ok(dimensions.rows < 29);\n    });\n\n    test('small', async function(): Promise<any> {\n      await setDimensions(1, 1);\n      deepEqual(await ctx.page.evaluate(`window.fit.proposeDimensions()`), {\n        cols: 2,\n        rows: 1\n      });\n    });\n\n    test('hidden', async function(): Promise<any> {\n      await ctx.proxy.dispose();\n      await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);\n      await ctx.page.evaluate(`window.term = new Terminal()`);\n      await ctx.page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);\n      await setDimensions();\n      const dimensions: { cols: number, rows: number } | undefined = await ctx.page.evaluate(`window.fit.proposeDimensions()`);\n      // The value of dims will be undefined if the char measure strategy falls back to the DOM\n      // method, so only assert if it's not undefined.\n      if (dimensions) {\n        ok(dimensions.cols > 85);\n        ok(dimensions.cols < 88);\n        ok(dimensions.rows > 24);\n        ok(dimensions.rows < 29);\n      }\n    });\n  });\n\n  test.describe('fit', () => {\n    test('default', async function(): Promise<any> {\n      await setDimensions();\n      await ctx.page.evaluate(`window.fit.fit()`);\n      const cols: number = await ctx.proxy.cols;\n      const rows: number = await ctx.proxy.rows;\n      ok(cols > 85);\n      ok(cols < 88);\n      ok(rows > 24);\n      ok(rows < 29);\n    });\n\n    test('width', async function(): Promise<any> {\n      await setDimensions(1008);\n      await ctx.page.evaluate(`window.fit.fit()`);\n      const cols: number = await ctx.proxy.cols;\n      const rows: number = await ctx.proxy.rows;\n      ok(cols > 108);\n      ok(cols < 111);\n      ok(rows > 24);\n      ok(rows < 29);\n    });\n\n    test('small', async function(): Promise<any> {\n      await setDimensions(1, 1);\n      await ctx.page.evaluate(`window.fit.fit()`);\n      strictEqual(await ctx.proxy.cols, 2);\n      strictEqual(await ctx.proxy.rows, 1);\n    });\n  });\n});\n\nasync function setDimensions(width: number = 800, height: number = 450): Promise<void> {\n  await ctx.page.evaluate(`\n    document.querySelector('#terminal-container').style.width='${width}px';\n    document.querySelector('#terminal-container').style.height='${height}px';\n    document.querySelector('#terminal-container').style.display='';\n  `);\n  // HACK: Await a short period as hiding #terminal-container can mess with other tests\n  await timeout(500);\n}\n"
  },
  {
    "path": "addons/addon-fit/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-fit/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-fit/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-fit/typings/addon-fit.d.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-fit' {\n  /**\n   * An xterm.js addon that enables resizing the terminal to the dimensions of\n   * its containing element.\n   */\n  export class FitAddon implements ITerminalAddon {\n    /**\n     * Creates a new fit addon.\n     */\n    constructor();\n\n    /**\n     * Activates the addon\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n\n    /**\n     * Resizes the terminal to the dimensions of its containing element.\n     */\n    public fit(): void;\n\n    /**\n     * Gets the proposed dimensions that will be used for a fit.\n     */\n    public proposeDimensions(): ITerminalDimensions | undefined;\n  }\n\n  /**\n   * Represents the dimensions of a terminal.\n   */\n  export interface ITerminalDimensions {\n    /**\n     * The number of rows in the terminal.\n     */\n    rows: number;\n\n    /**\n     * The number of columns in the terminal.\n     */\n    cols: number;\n  }\n}\n"
  },
  {
    "path": "addons/addon-fit/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'FitAddon';\nconst mainFile = 'addon-fit.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-image/.gitignore",
    "content": "node_modules/\n*.swp\n.lock-wscript\nlib/\nout/\nout-test/\nout-worker/\n.nyc_output/\nMakefile.gyp\n*.Makefile\n*.target.gyp.mk\n*.node\nexample/*.log\ndocs/\nnpm-debug.log\n/.idea/\n.env\nbuild/\n.DS_Store\nyarn.lock\n\n# Keep bundled code out of Git\ndist/\ndemo/dist/\n\n# skip benchmark & inwasm folders, except builds\n.benchmark/\ntimeline/\ninwasm-sdks/\ninwasm-builds/\n!inwasm-builds/**/final.wat\n!inwasm-builds/**/final.wasm\n!inwasm-builds/**/definition.json\n"
  },
  {
    "path": "addons/addon-image/.npmignore",
    "content": ".github\nfixture\noverwrite\nbootstrap.sh\ntsconfig.json\nwebpack.config.js\ntest\nout-test\nout-esbuild\nout-esbuild-test\ninwasm-sdks\ninwasm-builds\n"
  },
  {
    "path": "addons/addon-image/LICENSE",
    "content": "Copyright (c) 2019, 2020, 2021, 2022, 2023 The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-image/README.md",
    "content": "## @xterm/addon-image\n\nInline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).\n\n\n![](fixture/example.png)\n\n\n### Install from npm\n\n```bash\nnpm install --save @xterm/addon-image\n```\n\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { ImageAddon, IImageAddonOptions } from '@xterm/addon-image';\n\n// customize as needed (showing addon defaults)\nconst customSettings: IImageAddonOptions = {\n  enableSizeReports: true,    // whether to enable CSI t reports (see below)\n  pixelLimit: 16777216,       // max. pixel size of a single image\n  sixelSupport: true,         // enable sixel support\n  sixelScrolling: true,       // whether to scroll on image output\n  sixelPaletteLimit: 256,     // initial sixel palette size\n  sixelSizeLimit: 25000000,   // size limit of a single sixel sequence\n  storageLimit: 128,          // FIFO storage limit in MB\n  showPlaceholder: true,      // whether to show a placeholder for evicted images\n  iipSupport: true,           // enable iTerm IIP support\n  iipSizeLimit: 20000000,     // size limit of a single IIP sequence\n  kittySupport: true,         // enable Kitty graphics support\n  kittySizeLimit: 20000000    // size limit of a single Kitty sequence\n}\n\n// initialization\nconst terminal = new Terminal();\nconst imageAddon = new ImageAddon(customSettings);\nterminal.loadAddon(imageAddon);\n```\n\n### General Notes\n\n- *IMPORTANT:* The worker approach as done in previous releases got removed.\n  The addon contructor no longer expects a worker path as first argument.\n\n- By default the addon will activate these `windowOptions` reports on the terminal:\n  - getWinSizePixels (CSI 14 t)\n  - getCellSizePixels (CSI 16 t)\n  - getWinSizeChars (CSI 18 t)\n  \n  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.\n\n\n### Operation Modes\n\n- **SIXEL Support**  \n  Set by default, change it with `{sixelSupport: true}`.\n\n- **Scrolling On | Off**  \n  By default scrolling is on, thus an image will advance the cursor at the bottom if needed.\n  (see cursor positioning).\n\n  If scrolling is off, the image gets painted from the top left of the current viewport\n  and might be truncated if the image exceeds the viewport size.\n  The cursor position does not change.\n\n  You can customize this behavior with the constructor option `{sixelScrolling: false}`\n  or with `DECSET 80` (off, binary: `\\x1b [ ? 80 h`) and\n  `DECRST 80` (on, binary: `\\x1b [ ? 80 l`) during runtime.\n\n- **Cursor Positioning**  \n  If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).\n  Other cursor positioning modes as used by xterm or mintty are not supported.\n\n- **SIXEL Palette Handling**  \n  By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).\n  The limit can be increased to a maximum of 4096 registers (via `sixelPaletteLimit`).\n\n  The default palette is a mixture of VT340 colors (lower 16 registers), xterm colors (up to 256) and zeros (up to 4096).\n  There is no private/shared palette distinction, palette colors are always carried over from a previous sixel image.\n  Restoring the default palette size and colors is possible with `XTSMGRAPHICS 1 ; 2` (binary: `\\x1b[?1;2S`).\n  It gets also restored automatically on RIS and DECSTR.\n\n  Other than on older terminals, the underlying SIXEL library applies colors immediately to individual pixels\n  (*printer mode*), thus it is technically possible to use more colors in one image than the palette has color slots.\n  This feature is called *high-color* in libsixel.\n\n  A terminal wide shared palette mode with late indexed coloring of the output is not supported,\n  therefore palette animations cannot be used.\n\n- **SIXEL Raster Attributes Handling**  \n  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.\n\n  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.\n\n- **IIP Support (iTerm's Inline Image Protocol)**  \n  Set by default, change it with `{iipSupport: true}`.\n\n  The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):\n  - Supported formats: PNG, JPEG and GIF\n  - No animation support.\n  - Image type hinting is not supported (always deducted from data header).\n  - File download is not supported.\n  - Filename gets parsed but not used.\n  - Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).\n  - Payload size may not exceed CEIL(sizeParameter * 4 / 3).\n  - Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).\n  - Image pixel size is restricted by `pixelLimit` (pre- and post resizing).\n  - Size parameter is restricted by `iipSizeLimit`.\n  - Cursor positioning behaves the same as for sixel (see above).\n\n\n### Storage and Drawing Settings\n\nThe 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.\n\nThe addon exposes two properties to interact with the storage limits at runtime:\n- `storageLimit`  \n  Change the value to your needs at runtime. This is especially useful, if you have multiple terminal\n  instances running, that all add to one upper memory limit.\n- `storageUsage`  \n  Inspect the current memory usage of the image storage.\n\nBy default the addon will show a placeholder pattern for evicted images that are still part\nof the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.\n\n### Image Data Retrieval\n\nThe addon provides the following API endpoints to retrieve raw image data as canvas:\n\n- `getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined`  \n  Returns the canvas containing the original image data (not resized) at the given buffer position.\n  The buffer position is the 0-based absolute index (including scrollback at top).\n\n- `extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined`  \n  Returns a canvas containing the actual single tile image data (maybe resized) at the given buffer position.\n  The buffer position is the 0-based absolute index (including scrollback at top).\n  Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.\n\n### Memory Usage\n\nThe 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:\n- Incomming image data chunk at `term.write` (terminal)  \n  `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.\n- Sequence Parser (terminal)  \n  The parser operates on a buffer containing up to 2^17 codepoints (~0.5 MB).\n- Sequence Handler - Chunk Decoding (addon)  \n  Image data chunks are processed immediately by the SIXEL decoder (streamlined). The decoder allocates memory for image\n  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).\n- Sequence Handler - Image Finalization (addon) \n  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.\n- Image Storage (addon)  \n  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_.\n\nFollowing the steps above, a rough estimation of maximum memory usage by the addon can be calculated with these formulas (in bytes):\n```typescript\n// storage alone\nconst storageBytes = storageUsage * storageLimit * 1024 * 1024;\n// decoding alone\nconst decodingBytes = sixelSizeLimit + 2 * (pixelLimit * 4);\n\n// totals\n// inactive decoding\nconst totalInactive = storageBytes;\n// active decoding\nconst totalActive = storageBytes + decodingBytes;\n```\n\nNote 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.  \nFurther 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%).\n\n_Why should I care about memory usage at all?_  \nWell 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.\n\n_How can I adjust the memory usage?_  \n- `pixelLimit`  \n  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.\n- `storageLimit`  \n  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.\n- `sixelSizeLimit`  \n  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.\n\n\n### Terminal Interaction\n\n- Images already on the terminal screen will reshape on font-rescaling to keep the terminal cell coverage intact.\n  This behavior might diverge from other terminals, but is in general the more desired one.\n- On terminal resize images may expand to the right automatically, if they were right-truncated before.\n  They never expand to the bottom, if they were bottom-truncated before (e.g. from scrolling-off).\n- Text autowrapping from terminal resize may break and wrap images into multiple parts. This is unfortunate,\n  but cannot be avoided, while sticking to the stronger terminal text-grid mechanics.\n  (Yes images are a second class citizen on a mainly text-driven interface.)\n- Characters written over an image will erase the image information for affected cells.\n- Images are painted above BG/FG data not erasing it. More advanced \"composition tricks\" like painting images\n  below FG/BG data are not possible. (We currently dont hook into BG/FG rendering itself.)\n- Previous image data at a cell will be erased on new image data. (We currently have no transparency composition.)\n\n\n### Performance & Latency\n\n- Performance should be good enough for occasional SIXEL output from REPLs, up to downscaled movies\n  from `mpv` with its SIXEL renderer (tested in the demo). For 3rd party xterm.js integrations this\n  furthermore depends highly on the overall incoming data throughput.\n- Image processing has a high latency. Most of the latency though is inherited from xterm.js' incoming data route\n  (PTY -> server process -> websocket -> xterm.js async parsing), where every step creates more waiting time.\n  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.\n\n\n### Status\n\n- Sixel support and image handling in xterm.js is considered beta quality.\n- IIP support is in alpha stage. Please file a bug for any awkwardities.\n\n\n### Changelog\n\n- 0.5.0 integrate with xtermjs base repo (at v0.4.3)\n- 0.4.3 defer canvas creation\n- 0.4.2 fix image canvas resize\n- 0.4.1 compat release for xterm.js 5.2.0\n- 0.4.0 IIP support\n- 0.3.1 compat release for xterm.js 5.1.0\n- 0.3.0 important change: worker removed from addon\n- 0.2.0 compat release for xterm.js 5.0.0\n- 0.1.3 bugfix: avoid striping\n- 0.1.2 bugfix: reset clear flag\n- 0.1.1 bugfixes:\n  - clear sticky image tiles on render\n  - create folder from bootstrap.sh\n  - fix peer dependency in package.json\n"
  },
  {
    "path": "addons/addon-image/fixture/endless.sh",
    "content": "#!/bin/bash\n\n# sixel endless mode\n# Should print an endless sine curve, abort with Ctrl-C.\n\nperiod=200\namplitude=50\n\nsixels=(@\\$ A\\$ C\\$ G\\$ O\\$ _\\$-)\npi=$(echo \"scale=10; 4*a(1)\" | bc -l)\nrun=true\ntrap run=false INT\n\necho -ne \"\\x1bP0;0;0q\\\"1;1#1;2;100;0;0#1\"\ny=0\nwhile $run\ndo\n  x=$(echo \"s(2*${pi}*${y}/${period})*${amplitude}+2*${amplitude}+0.5\" | bc -l)\n  echo -ne \"!${x%%.*}?${sixels[$((y%6))]}\"\n  (( y++ ))\ndone\necho -e \"\\x1b\\\\\"\n"
  },
  {
    "path": "addons/addon-image/fixture/gcrglf.sh",
    "content": "#!/bin/bash\n\n# GLF should move the text cursor downwards,\n# even if no pixels were modified,\n# when sixel scrolling is on.\n\necho -n $'\\e[?80l'\t\t# Ensure sixel scrolling is on (disable DECSDM)\n\nclear\necho \"A test of Sixel GLF (Graphics Line Feed) when sixel scrolling is on\"\necho\n\n# Move cursor down three using GLF (\"-\")\necho -e '\\x1bPq$-$-$-$-\\x1b\\\\'\n\n# Show a single sixel line that says, \"Your terminal ->\"\ncat <<'EOF'\n\u001bP0;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?@-\u001b\\\nEOF\n\nsleep 1\ntput cup 2 30\n\n# Show four lines saying, \"<- A genuine VT340 would end here\"\ncat <<'EOF'\n\u001bP0;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@$-\u001b\\This text should be indented.\nEOF\n\n# For more details, please see:\n# https://github.com/jerch/xterm-addon-image/issues/37\n\n# By default, sixel is in a 2:1 aspect ratio, which means\n# every sixel graphics linefeed (GLF) adds another 12 pixels.\n# With four GLFs, we have five sixel lines = 5 * 12 =  60 pixels.\n\n# Which text line that ends up on depends upon the height of your\n# font. On the VT340, the font is 20 pixels high. \n\n# On a VT340 sixel scrolling is on by default. DEC refers to this by\n# two different names \"sixel scrolling\" and its negation, \"DECSDM\"\n# (Sixel Display Mode). They control the same thing, so when DECSDM is\n# on, sixel scrolling is off, and vice versa. \n\n# When DECSDM is on, the graphic line feeds do not affect the text cursor.\n\n\n# How hackerb9 created the two sixel test images:\n#\n# 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\n#\n# 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\n\n"
  },
  {
    "path": "addons/addon-image/fixture/growing_rect.js",
    "content": "const sixelEncode = require('../node_modules/sixel/lib/SixelEncoder').image2sixel;\nconst toRGBA8888 = require('../node_modules/sixel/lib/Colors').toRGBA8888;\n\nfunction createRect(size, color) {\n  const pixels = new Uint32Array(size * size);\n  pixels.fill(toRGBA8888(...color));\n  return sixelEncode(new Uint8ClampedArray(pixels.buffer), size, size);\n}\n\nfunction createRectMinusOne(size, color) {\n  const pixels = new Uint32Array(size * size);\n  if (size - 1) {\n    const sub = new Uint32Array(size - 1);\n    sub.fill(toRGBA8888(...color));\n    const last = size * (size - 1);\n    for (let y = 0; y < last; y += size) {\n      pixels.set(sub, y);\n    }\n  }\n  return sixelEncode(new Uint8ClampedArray(pixels.buffer), size, size);\n}\n\nasync function main() {\n  // clear + cursor and sixelScrolling off\n  process.stdout.write('\\x1b[2J\\x1b[?25;80h');\n\n  for (let i = 1; i < 300; ++i) {\n    await new Promise(res => setTimeout(() => {\n      process.stdout.write(createRect(i, [0, 255, 0]));\n      res();\n    }, 5));\n  }\n  for (let i = 299; i >= 1; --i) {\n    await new Promise(res => setTimeout(() => {\n      process.stdout.write(createRectMinusOne(i, [0, 255, 0]));\n      res();\n    }, 5));\n  }\n\n  // re-enable cursor and sixel scrolling\n  process.stdout.write('\\x1b[2J\\x1b[?25;80l');\n}\n\nmain();\n"
  },
  {
    "path": "addons/addon-image/fixture/iip/palette.iip",
    "content": "\u001b]1337;File=inline=1;size=525;name=Li4vcGFsZXR0ZS5wbmc=:iVBORw0KGgoAAAANSUhEUgAAAoAAAABQCAYAAACJbMQlAAAABmJLR0QA/wD/AP+gvaeTAAABwklEQVR4nO3dwQnEMAwAQedIIem/yVwLBj0C3pm3uAO/Fj2ia631rh3P1lRu7vnof0+Z836zOe83m/N+sznvN5vzfrM57zeb+23+HAAAhxCAAAAxAhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIi59z8tDQDACWwAAQBiBCAAQIwABACIEYAAADECEAAgRgACAMQIQACAGAEIABAjAAEAYlwCAQCIsQEEAIgRgAAAMQIQACBGAAIAxAhAAIAYAQgAECMAAQBiBCAAQIwABACIcQkEACDGBhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIgRgAAAMQIQACDGJRAAgBgbQACAGAEIABAjAAEAYgQgAECMAAQAiBGAAAAxAhAAIEYAAgDECEAAgBiXQAAAYmwAAQBiBCAAQIwABACIEYAAADECEAAgRgACAMQIQACAGAEIABAjAAEAYlwCAQCIsQEEAIgRgAAAMQIQACBGAAIAxAhAAIAYAQgAECMAAQBiBCAAQIwABACIcQkEACDGBhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIgRgAAAMQIQACDmDz4lC02AaqMRAAAAAElFTkSuQmCC\u0007\n"
  },
  {
    "path": "addons/addon-image/fixture/iip/spinfox.iip",
    "content": "\u001b]1337;File=inline=1;size=613321;name=Li4vdGVzdGltYWdlcy9zcGluZm94LnBuZw==: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\u0007\n"
  },
  {
    "path": "addons/addon-image/fixture/iip/w3c_gif.iip",
    "content": "\u001b]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=\u0007\n"
  },
  {
    "path": "addons/addon-image/fixture/iip/w3c_jpg.iip",
    "content": "\u001b]1337;File=inline=1;size=2159;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYuanBn: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\u0007\n"
  },
  {
    "path": "addons/addon-image/fixture/iip/w3c_png.iip",
    "content": "\u001b]1337;File=inline=1;size=1658;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYucG5n: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\u0007\n"
  },
  {
    "path": "addons/addon-image/fixture/inspect_palette.sh",
    "content": "#!/bin/bash\n\nfunction print_palette() {\n  L=$(( LOWER / 256))\n  U=$(( (UPPER-1) / 256))\n  for ((p = $L; p <= $U; p++))\n  do\n    echo \"slot $((p*256))..$((p*256+255)):\"\n    echo -ne \"\\x1bP;1q\"\n    for i in {0..15}\n    do\n      a=$((i * 16 + p * 256))\n      for j in {0..15}\n      do\n        echo -ne \"#$((a+j))!6~\"\n      done\n      echo -ne \"\\$-\"\n    done\n    echo -e \"\\x1b\\\\\"\n  done\n}\n\ncolors=undefined\nmax_colors=undefined\n\necho \"Terminal Reports (XTSMGRAPHICS):\"\nIFS=\";\" read -a REPLY -s -t 1 -d \"S\" -p $'\\e[?1;1;0S'\n[[ ${REPLY[1]} == \"0\" ]] && colors=${REPLY[2]}\necho \"active colors: ${colors}\"\n\nIFS=\";\" read -a REPLY -s -t 1 -d \"S\" -p $'\\e[?1;4;0S'\n[[ ${REPLY[1]} == \"0\" ]] && max_colors=${REPLY[2]}\necho \"max colors   : ${max_colors}\"\necho\n\n\n# query up to colors by default\n# if colors is undefined (no XTSMGRAPHICS), assume 256\nARG1=${1:-${colors}}\nif [[ $colors == \"undefined\" ]]\nthen\n  ARG1=${1:-256}\nfi\nLOWER=0\nUPPER=$ARG1\nARG2=${2:-undefined}\nif [[ $ARG2 != \"undefined\" ]]\nthen\n  LOWER=ARG1\n  UPPER=ARG2\nfi\n\nif [[ $colors != \"undefined\" ]]\nthen\n  if [[ $colors -lt $UPPER ]] || [[ $colors -lt 256 ]]\n  then\n    echo -e \"\\x1b[33mNote: Active colors is smaller than test range.\"\n    echo -e \"A spec-conform terminal may repeat colors in 'slot mod ${colors}'.\\x1b[m\"\n    echo\n  fi\nelse\n  echo -e \"\\x1b[33mNote: Cannot query active colors.\"\n  echo -e \"The terminal may repeat colors beyond it max slot (e.g. slot mod 16).\\x1b[m\"\n  echo\nfi\n\nprint_palette\n"
  },
  {
    "path": "addons/addon-image/fixture/overdraw.sh",
    "content": "#!/bin/bash\n\nfunction smiling_smiley() {\n  echo -ne '\\x1bP;2q\"1;1;60;60\n#6!60~$-\n!60~$-\n!60~$\n!15?#1!4]!22?!4]$-\n#6!60~$-\n!60~$-\n!60~$-\n!60~$\n!15?#1!4~!22?!4~$-\n#6!60~$\n!15?#1!30N$-\n#6!60~$-\n!60~$-\n'\n  echo -ne '\\x1b\\\\'\n}\n\nfunction indifferent_smiley() {\n  echo -ne '\\x1bP;2q\"1;1;60;60\n#6!60~$-\n!60~$-\n!60~$\n!15?#1!4]!22?!4]$-\n#6!60~$-\n!60~$-\n!60~$-\n!60~$-\n!60~$\n!15?#1!30N$-\n#6!60~$-\n!60~$-\n'\n  echo -ne '\\x1b\\\\'\n}\n\nfunction sad_smiley() {\n  echo -ne '\\x1bP;2q\"1;1;60;60\n#6!60~$-\n!60~$-\n!60~$\n!15?#1!4]!22?!4]$-\n#6!60~$-\n!60~$-\n!60~$-\n!60~$-\n!60~$\n!15?#1!30N$\n!15?#1!4o!22?!4o$-\n#6!60~$\n!15?#1!4B!22?!4B$-\n#6!60~$-\n'\n  echo -ne '\\x1b\\\\'\n}\n\nfunction smiling_smiley_slim() {\n  echo -ne '\\x1bP;1q\"1;1;60;60\n$-\n$-\n$-\n$-\n$-\n$-\n!15?#1!4~!22?!4~$-\n!15?#1!30N\n'\n  echo -ne '\\x1b\\\\'\n}\n\nfunction indifferent_smiley_slim() {\n  echo -ne '\\x1bP;1q\"1;1;60;60\n$-\n$-\n$-\n$-\n$-\n$-\n!15?#6!4~!22?!4~$-\n!15?!4o!22?!4o$ !15?#1!30N$-\n!15?#6!4B!22?!4B\n'\n  echo -ne '\\x1b\\\\'\n}\n\nfunction sad_smiley_slim() {\n  echo -ne '\\x1bP;1q\"1;1;60;60\n$-\n$-\n$-\n$-\n$-\n$-\n$-\n!15?#1!30N$\n!15?#1!4o!22?!4o$-\n!15?#1!4B!22?!4B$-\n'\n  echo -ne '\\x1b\\\\'\n}\n\nfunction full() {\n  smiling_smiley\n  sleep .5\n  indifferent_smiley\n  sleep .5\n  sad_smiley\n  sleep .5\n  indifferent_smiley\n  sleep .5\n  smiling_smiley\n}\n\nfunction slim() {\n  smiling_smiley\n  sleep .5\n  indifferent_smiley_slim\n  sleep .5\n  sad_smiley_slim\n  sleep .5\n  indifferent_smiley_slim\n  sleep .5\n  smiling_smiley_slim\n}\n\n# clear screen and place cursor to 1;10\necho -ne '\\x1b[2J\\x1b[10;1H'\n\n# switch sixel scrolling off\necho -ne '\\x1b[?80h'\n\ncase \"$1\" in\n  full ) full ;;\n  slim ) slim ;;\nesac\n\n# re-enable sixel scrolling\necho -ne '\\x1b[?80l'\n"
  },
  {
    "path": "addons/addon-image/fixture/palette.sixel",
    "content": "\u001bP0;0;q\"1;1;640;80#0;2;0;0;0#1;2;0;13;0#2;2;0;25;0#3;2;0;38;0#4;2;0;50;0#5;2;0;63;0#6;2;0;75;0#7;2;0;88;0#8;2;13;0;0#9;2;13;13;0#10;2;13;25;0#11;2;13;38;0#12;2;13;50;0#13;2;13;63;0#14;2;13;75;0#15;2;13;88;0#16;2;25;0;0#17;2;25;13;0#18;2;25;25;0#19;2;25;38;0#20;2;25;50;0#21;2;25;63;0#22;2;25;75;0#23;2;25;88;0#24;2;38;0;0#25;2;38;13;0#26;2;38;25;0#27;2;38;38;0#28;2;38;50;0#29;2;38;63;0#30;2;38;75;0#31;2;38;88;0#32;2;50;0;0#33;2;50;13;0#34;2;50;25;0#35;2;50;38;0#36;2;50;50;0#37;2;50;63;0#38;2;50;75;0#39;2;50;88;0#40;2;63;0;0#41;2;63;13;0#42;2;63;25;0#43;2;63;38;0#44;2;63;50;0#45;2;63;63;0#46;2;63;75;0#47;2;63;88;0#48;2;75;0;0#49;2;75;13;0#50;2;75;25;0#51;2;75;38;0#52;2;75;50;0#53;2;75;63;0#54;2;75;75;0#55;2;75;88;0#56;2;88;0;0#57;2;88;13;0#58;2;88;25;0#59;2;88;38;0#60;2;88;50;0#61;2;88;63;0#62;2;88;75;0#63;2;88;88;0#64;2;0;0;13#65;2;0;13;13#66;2;0;25;13#67;2;0;38;13#68;2;0;50;13#69;2;0;63;13#70;2;0;75;13#71;2;0;88;13#72;2;13;0;13#73;2;13;13;13#74;2;13;25;13#75;2;13;38;13#76;2;13;50;13#77;2;13;63;13#78;2;13;75;13#79;2;13;88;13#80;2;25;0;13#81;2;25;13;13#82;2;25;25;13#83;2;25;38;13#84;2;25;50;13#85;2;25;63;13#86;2;25;75;13#87;2;25;88;13#88;2;38;0;13#89;2;38;13;13#90;2;38;25;13#91;2;38;38;13#92;2;38;50;13#93;2;38;63;13#94;2;38;75;13#95;2;38;88;13#96;2;50;0;13#97;2;50;13;13#98;2;50;25;13#99;2;50;38;13#100;2;50;50;13#101;2;50;63;13#102;2;50;75;13#103;2;50;88;13#104;2;63;0;13#105;2;63;13;13#106;2;63;25;13#107;2;63;38;13#108;2;63;50;13#109;2;63;63;13#110;2;63;75;13#111;2;63;88;13#112;2;75;0;13#113;2;75;13;13#114;2;75;25;13#115;2;75;38;13#116;2;75;50;13#117;2;75;63;13#118;2;75;75;13#119;2;75;88;13#120;2;88;0;13#121;2;88;13;13#122;2;88;25;13#123;2;88;38;13#124;2;88;50;13#125;2;88;63;13#126;2;88;75;13#127;2;88;88;13#128;2;0;0;25#129;2;0;13;25#130;2;0;25;25#131;2;0;38;25#132;2;0;50;25#133;2;0;63;25#134;2;0;75;25#135;2;0;88;25#136;2;13;0;25#137;2;13;13;25#138;2;13;25;25#139;2;13;38;25#140;2;13;50;25#141;2;13;63;25#142;2;13;75;25#143;2;13;88;25#144;2;25;0;25#145;2;25;13;25#146;2;25;25;25#147;2;25;38;25#148;2;25;50;25#149;2;25;63;25#150;2;25;75;25#151;2;25;88;25#152;2;38;0;25#153;2;38;13;25#154;2;38;25;25#155;2;38;38;25#156;2;38;50;25#157;2;38;63;25#158;2;38;75;25#159;2;38;88;25#160;2;50;0;25#161;2;50;13;25#162;2;50;25;25#163;2;50;38;25#164;2;50;50;25#165;2;50;63;25#166;2;50;75;25#167;2;50;88;25#168;2;63;0;25#169;2;63;13;25#170;2;63;25;25#171;2;63;38;25#172;2;63;50;25#173;2;63;63;25#174;2;63;75;25#175;2;63;88;25#176;2;75;0;25#177;2;75;13;25#178;2;75;25;25#179;2;75;38;25#180;2;75;50;25#181;2;75;63;25#182;2;75;75;25#183;2;75;88;25#184;2;88;0;25#185;2;88;13;25#186;2;88;25;25#187;2;88;38;25#188;2;88;50;25#189;2;88;63;25#190;2;88;75;25#191;2;88;88;25#192;2;0;0;38#193;2;0;13;38#194;2;0;25;38#195;2;0;38;38#196;2;0;50;38#197;2;0;63;38#198;2;0;75;38#199;2;0;88;38#200;2;13;0;38#201;2;13;13;38#202;2;13;25;38#203;2;13;38;38#204;2;13;50;38#205;2;13;63;38#206;2;13;75;38#207;2;13;88;38#208;2;25;0;38#209;2;25;13;38#210;2;25;25;38#211;2;25;38;38#212;2;25;50;38#213;2;25;63;38#214;2;25;75;38#215;2;25;88;38#216;2;38;0;38#217;2;38;13;38#218;2;38;25;38#219;2;38;38;38#220;2;38;50;38#221;2;38;63;38#222;2;38;75;38#223;2;38;88;38#224;2;50;0;38#225;2;50;13;38#226;2;50;25;38#227;2;50;38;38#228;2;50;50;38#229;2;50;63;38#230;2;50;75;38#231;2;50;88;38#232;2;63;0;38#233;2;63;13;38#234;2;63;25;38#235;2;63;38;38#236;2;63;50;38#237;2;63;63;38#238;2;63;75;38#239;2;63;88;38#240;2;75;0;38#241;2;75;13;38#242;2;75;25;38#243;2;75;38;38#244;2;75;50;38#245;2;75;63;38#246;2;75;75;38#247;2;75;88;38#248;2;88;0;38#249;2;88;13;38#250;2;88;25;38#251;2;88;38;38#252;2;88;50;38#253;2;88;63;38#254;2;88;75;38#255;2;88;88;38#256;2;0;0;50#257;2;0;13;50#258;2;0;25;50#259;2;0;38;50#260;2;0;50;50#261;2;0;63;50#262;2;0;75;50#263;2;0;88;50#264;2;13;0;50#265;2;13;13;50#266;2;13;25;50#267;2;13;38;50#268;2;13;50;50#269;2;13;63;50#270;2;13;75;50#271;2;13;88;50#272;2;25;0;50#273;2;25;13;50#274;2;25;25;50#275;2;25;38;50#276;2;25;50;50#277;2;25;63;50#278;2;25;75;50#279;2;25;88;50#280;2;38;0;50#281;2;38;13;50#282;2;38;25;50#283;2;38;38;50#284;2;38;50;50#285;2;38;63;50#286;2;38;75;50#287;2;38;88;50#288;2;50;0;50#289;2;50;13;50#290;2;50;25;50#291;2;50;38;50#292;2;50;50;50#293;2;50;63;50#294;2;50;75;50#295;2;50;88;50#296;2;63;0;50#297;2;63;13;50#298;2;63;25;50#299;2;63;38;50#300;2;63;50;50#301;2;63;63;50#302;2;63;75;50#303;2;63;88;50#304;2;75;0;50#305;2;75;13;50#306;2;75;25;50#307;2;75;38;50#308;2;75;50;50#309;2;75;63;50#310;2;75;75;50#311;2;75;88;50#312;2;88;0;50#313;2;88;13;50#314;2;88;25;50#315;2;88;38;50#316;2;88;50;50#317;2;88;63;50#318;2;88;75;50#319;2;88;88;50#320;2;0;0;63#321;2;0;13;63#322;2;0;25;63#323;2;0;38;63#324;2;0;50;63#325;2;0;63;63#326;2;0;75;63#327;2;0;88;63#328;2;13;0;63#329;2;13;13;63#330;2;13;25;63#331;2;13;38;63#332;2;13;50;63#333;2;13;63;63#334;2;13;75;63#335;2;13;88;63#336;2;25;0;63#337;2;25;13;63#338;2;25;25;63#339;2;25;38;63#340;2;25;50;63#341;2;25;63;63#342;2;25;75;63#343;2;25;88;63#344;2;38;0;63#345;2;38;13;63#346;2;38;25;63#347;2;38;38;63#348;2;38;50;63#349;2;38;63;63#350;2;38;75;63#351;2;38;88;63#352;2;50;0;63#353;2;50;13;63#354;2;50;25;63#355;2;50;38;63#356;2;50;50;63#357;2;50;63;63#358;2;50;75;63#359;2;50;88;63#360;2;63;0;63#361;2;63;13;63#362;2;63;25;63#363;2;63;38;63#364;2;63;50;63#365;2;63;63;63#366;2;63;75;63#367;2;63;88;63#368;2;75;0;63#369;2;75;13;63#370;2;75;25;63#371;2;75;38;63#372;2;75;50;63#373;2;75;63;63#374;2;75;75;63#375;2;75;88;63#376;2;88;0;63#377;2;88;13;63#378;2;88;25;63#379;2;88;38;63#380;2;88;50;63#381;2;88;63;63#382;2;88;75;63#383;2;88;88;63#384;2;0;0;75#385;2;0;13;75#386;2;0;25;75#387;2;0;38;75#388;2;0;50;75#389;2;0;63;75#390;2;0;75;75#391;2;0;88;75#392;2;13;0;75#393;2;13;13;75#394;2;13;25;75#395;2;13;38;75#396;2;13;50;75#397;2;13;63;75#398;2;13;75;75#399;2;13;88;75#400;2;25;0;75#401;2;25;13;75#402;2;25;25;75#403;2;25;38;75#404;2;25;50;75#405;2;25;63;75#406;2;25;75;75#407;2;25;88;75#408;2;38;0;75#409;2;38;13;75#410;2;38;25;75#411;2;38;38;75#412;2;38;50;75#413;2;38;63;75#414;2;38;75;75#415;2;38;88;75#416;2;50;0;75#417;2;50;13;75#418;2;50;25;75#419;2;50;38;75#420;2;50;50;75#421;2;50;63;75#422;2;50;75;75#423;2;50;88;75#424;2;63;0;75#425;2;63;13;75#426;2;63;25;75#427;2;63;38;75#428;2;63;50;75#429;2;63;63;75#430;2;63;75;75#431;2;63;88;75#432;2;75;0;75#433;2;75;13;75#434;2;75;25;75#435;2;75;38;75#436;2;75;50;75#437;2;75;63;75#438;2;75;75;75#439;2;75;88;75#440;2;88;0;75#441;2;88;13;75#442;2;88;25;75#443;2;88;38;75#444;2;88;50;75#445;2;88;63;75#446;2;88;75;75#447;2;88;88;75#448;2;0;0;88#449;2;0;13;88#450;2;0;25;88#451;2;0;38;88#452;2;0;50;88#453;2;0;63;88#454;2;0;75;88#455;2;0;88;88#456;2;13;0;88#457;2;13;13;88#458;2;13;25;88#459;2;13;38;88#460;2;13;50;88#461;2;13;63;88#462;2;13;75;88#463;2;13;88;88#464;2;25;0;88#465;2;25;13;88#466;2;25;25;88#467;2;25;38;88#468;2;25;50;88#469;2;25;63;88#470;2;25;75;88#471;2;25;88;88#472;2;38;0;88#473;2;38;13;88#474;2;38;25;88#475;2;38;38;88#476;2;38;50;88#477;2;38;63;88#478;2;38;75;88#479;2;38;88;88#480;2;50;0;88#481;2;50;13;88#482;2;50;25;88#483;2;50;38;88#484;2;50;50;88#485;2;50;63;88#486;2;50;75;88#487;2;50;88;88#488;2;63;0;88#489;2;63;13;88#490;2;63;25;88#491;2;63;38;88#492;2;63;50;88#493;2;63;63;88#494;2;63;75;88#495;2;63;88;88#496;2;75;0;88#497;2;75;13;88#498;2;75;25;88#499;2;75;38;88#500;2;75;50;88#501;2;75;63;88#502;2;75;75;88#503;2;75;88;88#504;2;88;0;88#505;2;88;13;88#506;2;88;25;88#507;2;88;38;88#508;2;88;50;88#509;2;88;63;88#510;2;88;75;88#511;2;88;88;88#0!10~$#1!10?!10~$#2!20?!10~$#3!30?!10~$#4!40?!10~$#5!50?!10~$#6!60?!10~$#7!70?!10~$#8!80?!10~$#9!90?!10~$#10!100?!10~$#11!110?!10~$#12!120?!10~$#13!130?!10~$#14!140?!10~$#15!150?!10~$#16!160?!10~$#17!170?!10~$#18!180?!10~$#19!190?!10~$#20!200?!10~$#21!210?!10~$#22!220?!10~$#23!230?!10~$#24!240?!10~$#25!250?!10~$#26!260?!10~$#27!270?!10~$#28!280?!10~$#29!290?!10~$#30!300?!10~$#31!310?!10~$#32!320?!10~$#33!330?!10~$#34!340?!10~$#35!350?!10~$#36!360?!10~$#37!370?!10~$#38!380?!10~$#39!390?!10~$#40!400?!10~$#41!410?!10~$#42!420?!10~$#43!430?!10~$#44!440?!10~$#45!450?!10~$#46!460?!10~$#47!470?!10~$#48!480?!10~$#49!490?!10~$#50!500?!10~$#51!510?!10~$#52!520?!10~$#53!530?!10~$#54!540?!10~$#55!550?!10~$#56!560?!10~$#57!570?!10~$#58!580?!10~$#59!590?!10~$#60!600?!10~$#61!610?!10~$#62!620?!10~$#63!630?!10~$-\n#0!10N$#64!10o$#1!10?!10N$#65!10?!10o$#2!20?!10N$#66!20?!10o$#3!30?!10N$#67!30?!10o$#4!40?!10N$#68!40?!10o$#5!50?!10N$#69!50?!10o$#6!60?!10N$#70!60?!10o$#7!70?!10N$#71!70?!10o$#8!80?!10N$#72!80?!10o$#9!90?!10N$#73!90?!10o$#10!100?!10N$#74!100?!10o$#11!110?!10N$#75!110?!10o$#12!120?!10N$#76!120?!10o$#13!130?!10N$#77!130?!10o$#14!140?!10N$#78!140?!10o$#15!150?!10N$#79!150?!10o$#16!160?!10N$#80!160?!10o$#17!170?!10N$#81!170?!10o$#18!180?!10N$#82!180?!10o$#19!190?!10N$#83!190?!10o$#20!200?!10N$#84!200?!10o$#21!210?!10N$#85!210?!10o$#22!220?!10N$#86!220?!10o$#23!230?!10N$#87!230?!10o$#24!240?!10N$#88!240?!10o$#25!250?!10N$#89!250?!10o$#26!260?!10N$#90!260?!10o$#27!270?!10N$#91!270?!10o$#28!280?!10N$#92!280?!10o$#29!290?!10N$#93!290?!10o$#30!300?!10N$#94!300?!10o$#31!310?!10N$#95!310?!10o$#32!320?!10N$#96!320?!10o$#33!330?!10N$#97!330?!10o$#34!340?!10N$#98!340?!10o$#35!350?!10N$#99!350?!10o$#36!360?!10N$#100!360?!10o$#37!370?!10N$#101!370?!10o$#38!380?!10N$#102!380?!10o$#39!390?!10N$#103!390?!10o$#40!400?!10N$#104!400?!10o$#41!410?!10N$#105!410?!10o$#42!420?!10N$#106!420?!10o$#43!430?!10N$#107!430?!10o$#44!440?!10N$#108!440?!10o$#45!450?!10N$#109!450?!10o$#46!460?!10N$#110!460?!10o$#47!470?!10N$#111!470?!10o$#48!480?!10N$#112!480?!10o$#49!490?!10N$#113!490?!10o$#50!500?!10N$#114!500?!10o$#51!510?!10N$#115!510?!10o$#52!520?!10N$#116!520?!10o$#53!530?!10N$#117!530?!10o$#54!540?!10N$#118!540?!10o$#55!550?!10N$#119!550?!10o$#56!560?!10N$#120!560?!10o$#57!570?!10N$#121!570?!10o$#58!580?!10N$#122!580?!10o$#59!590?!10N$#123!590?!10o$#60!600?!10N$#124!600?!10o$#61!610?!10N$#125!610?!10o$#62!620?!10N$#126!620?!10o$#63!630?!10N$#127!630?!10o$-\n#64!10~$#65!10?!10~$#66!20?!10~$#67!30?!10~$#68!40?!10~$#69!50?!10~$#70!60?!10~$#71!70?!10~$#72!80?!10~$#73!90?!10~$#74!100?!10~$#75!110?!10~$#76!120?!10~$#77!130?!10~$#78!140?!10~$#79!150?!10~$#80!160?!10~$#81!170?!10~$#82!180?!10~$#83!190?!10~$#84!200?!10~$#85!210?!10~$#86!220?!10~$#87!230?!10~$#88!240?!10~$#89!250?!10~$#90!260?!10~$#91!270?!10~$#92!280?!10~$#93!290?!10~$#94!300?!10~$#95!310?!10~$#96!320?!10~$#97!330?!10~$#98!340?!10~$#99!350?!10~$#100!360?!10~$#101!370?!10~$#102!380?!10~$#103!390?!10~$#104!400?!10~$#105!410?!10~$#106!420?!10~$#107!430?!10~$#108!440?!10~$#109!450?!10~$#110!460?!10~$#111!470?!10~$#112!480?!10~$#113!490?!10~$#114!500?!10~$#115!510?!10~$#116!520?!10~$#117!530?!10~$#118!540?!10~$#119!550?!10~$#120!560?!10~$#121!570?!10~$#122!580?!10~$#123!590?!10~$#124!600?!10~$#125!610?!10~$#126!620?!10~$#127!630?!10~$-\n#64!10B$#128!10{$#65!10?!10B$#129!10?!10{$#66!20?!10B$#130!20?!10{$#67!30?!10B$#131!30?!10{$#68!40?!10B$#132!40?!10{$#69!50?!10B$#133!50?!10{$#70!60?!10B$#134!60?!10{$#71!70?!10B$#135!70?!10{$#72!80?!10B$#136!80?!10{$#73!90?!10B$#137!90?!10{$#74!100?!10B$#138!100?!10{$#75!110?!10B$#139!110?!10{$#76!120?!10B$#140!120?!10{$#77!130?!10B$#141!130?!10{$#78!140?!10B$#142!140?!10{$#79!150?!10B$#143!150?!10{$#80!160?!10B$#144!160?!10{$#81!170?!10B$#145!170?!10{$#82!180?!10B$#146!180?!10{$#83!190?!10B$#147!190?!10{$#84!200?!10B$#148!200?!10{$#85!210?!10B$#149!210?!10{$#86!220?!10B$#150!220?!10{$#87!230?!10B$#151!230?!10{$#88!240?!10B$#152!240?!10{$#89!250?!10B$#153!250?!10{$#90!260?!10B$#154!260?!10{$#91!270?!10B$#155!270?!10{$#92!280?!10B$#156!280?!10{$#93!290?!10B$#157!290?!10{$#94!300?!10B$#158!300?!10{$#95!310?!10B$#159!310?!10{$#96!320?!10B$#160!320?!10{$#97!330?!10B$#161!330?!10{$#98!340?!10B$#162!340?!10{$#99!350?!10B$#163!350?!10{$#100!360?!10B$#164!360?!10{$#101!370?!10B$#165!370?!10{$#102!380?!10B$#166!380?!10{$#103!390?!10B$#167!390?!10{$#104!400?!10B$#168!400?!10{$#105!410?!10B$#169!410?!10{$#106!420?!10B$#170!420?!10{$#107!430?!10B$#171!430?!10{$#108!440?!10B$#172!440?!10{$#109!450?!10B$#173!450?!10{$#110!460?!10B$#174!460?!10{$#111!470?!10B$#175!470?!10{$#112!480?!10B$#176!480?!10{$#113!490?!10B$#177!490?!10{$#114!500?!10B$#178!500?!10{$#115!510?!10B$#179!510?!10{$#116!520?!10B$#180!520?!10{$#117!530?!10B$#181!530?!10{$#118!540?!10B$#182!540?!10{$#119!550?!10B$#183!550?!10{$#120!560?!10B$#184!560?!10{$#121!570?!10B$#185!570?!10{$#122!580?!10B$#186!580?!10{$#123!590?!10B$#187!590?!10{$#124!600?!10B$#188!600?!10{$#125!610?!10B$#189!610?!10{$#126!620?!10B$#190!620?!10{$#127!630?!10B$#191!630?!10{$-\n#128!10~$#129!10?!10~$#130!20?!10~$#131!30?!10~$#132!40?!10~$#133!50?!10~$#134!60?!10~$#135!70?!10~$#136!80?!10~$#137!90?!10~$#138!100?!10~$#139!110?!10~$#140!120?!10~$#141!130?!10~$#142!140?!10~$#143!150?!10~$#144!160?!10~$#145!170?!10~$#146!180?!10~$#147!190?!10~$#148!200?!10~$#149!210?!10~$#150!220?!10~$#151!230?!10~$#152!240?!10~$#153!250?!10~$#154!260?!10~$#155!270?!10~$#156!280?!10~$#157!290?!10~$#158!300?!10~$#159!310?!10~$#160!320?!10~$#161!330?!10~$#162!340?!10~$#163!350?!10~$#164!360?!10~$#165!370?!10~$#166!380?!10~$#167!390?!10~$#168!400?!10~$#169!410?!10~$#170!420?!10~$#171!430?!10~$#172!440?!10~$#173!450?!10~$#174!460?!10~$#175!470?!10~$#176!480?!10~$#177!490?!10~$#178!500?!10~$#179!510?!10~$#180!520?!10~$#181!530?!10~$#182!540?!10~$#183!550?!10~$#184!560?!10~$#185!570?!10~$#186!580?!10~$#187!590?!10~$#188!600?!10~$#189!610?!10~$#190!620?!10~$#191!630?!10~$-\n#192!10~$#193!10?!10~$#194!20?!10~$#195!30?!10~$#196!40?!10~$#197!50?!10~$#198!60?!10~$#199!70?!10~$#200!80?!10~$#201!90?!10~$#202!100?!10~$#203!110?!10~$#204!120?!10~$#205!130?!10~$#206!140?!10~$#207!150?!10~$#208!160?!10~$#209!170?!10~$#210!180?!10~$#211!190?!10~$#212!200?!10~$#213!210?!10~$#214!220?!10~$#215!230?!10~$#216!240?!10~$#217!250?!10~$#218!260?!10~$#219!270?!10~$#220!280?!10~$#221!290?!10~$#222!300?!10~$#223!310?!10~$#224!320?!10~$#225!330?!10~$#226!340?!10~$#227!350?!10~$#228!360?!10~$#229!370?!10~$#230!380?!10~$#231!390?!10~$#232!400?!10~$#233!410?!10~$#234!420?!10~$#235!430?!10~$#236!440?!10~$#237!450?!10~$#238!460?!10~$#239!470?!10~$#240!480?!10~$#241!490?!10~$#242!500?!10~$#243!510?!10~$#244!520?!10~$#245!530?!10~$#246!540?!10~$#247!550?!10~$#248!560?!10~$#249!570?!10~$#250!580?!10~$#251!590?!10~$#252!600?!10~$#253!610?!10~$#254!620?!10~$#255!630?!10~$-\n#192!10N$#256!10o$#193!10?!10N$#257!10?!10o$#194!20?!10N$#258!20?!10o$#195!30?!10N$#259!30?!10o$#196!40?!10N$#260!40?!10o$#197!50?!10N$#261!50?!10o$#198!60?!10N$#262!60?!10o$#199!70?!10N$#263!70?!10o$#200!80?!10N$#264!80?!10o$#201!90?!10N$#265!90?!10o$#202!100?!10N$#266!100?!10o$#203!110?!10N$#267!110?!10o$#204!120?!10N$#268!120?!10o$#205!130?!10N$#269!130?!10o$#206!140?!10N$#270!140?!10o$#207!150?!10N$#271!150?!10o$#208!160?!10N$#272!160?!10o$#209!170?!10N$#273!170?!10o$#210!180?!10N$#274!180?!10o$#211!190?!10N$#275!190?!10o$#212!200?!10N$#276!200?!10o$#213!210?!10N$#277!210?!10o$#214!220?!10N$#278!220?!10o$#215!230?!10N$#279!230?!10o$#216!240?!10N$#280!240?!10o$#217!250?!10N$#281!250?!10o$#218!260?!10N$#282!260?!10o$#219!270?!10N$#283!270?!10o$#220!280?!10N$#284!280?!10o$#221!290?!10N$#285!290?!10o$#222!300?!10N$#286!300?!10o$#223!310?!10N$#287!310?!10o$#224!320?!10N$#288!320?!10o$#225!330?!10N$#289!330?!10o$#226!340?!10N$#290!340?!10o$#227!350?!10N$#291!350?!10o$#228!360?!10N$#292!360?!10o$#229!370?!10N$#293!370?!10o$#230!380?!10N$#294!380?!10o$#231!390?!10N$#295!390?!10o$#232!400?!10N$#296!400?!10o$#233!410?!10N$#297!410?!10o$#234!420?!10N$#298!420?!10o$#235!430?!10N$#299!430?!10o$#236!440?!10N$#300!440?!10o$#237!450?!10N$#301!450?!10o$#238!460?!10N$#302!460?!10o$#239!470?!10N$#303!470?!10o$#240!480?!10N$#304!480?!10o$#241!490?!10N$#305!490?!10o$#242!500?!10N$#306!500?!10o$#243!510?!10N$#307!510?!10o$#244!520?!10N$#308!520?!10o$#245!530?!10N$#309!530?!10o$#246!540?!10N$#310!540?!10o$#247!550?!10N$#311!550?!10o$#248!560?!10N$#312!560?!10o$#249!570?!10N$#313!570?!10o$#250!580?!10N$#314!580?!10o$#251!590?!10N$#315!590?!10o$#252!600?!10N$#316!600?!10o$#253!610?!10N$#317!610?!10o$#254!620?!10N$#318!620?!10o$#255!630?!10N$#319!630?!10o$-\n#256!10~$#257!10?!10~$#258!20?!10~$#259!30?!10~$#260!40?!10~$#261!50?!10~$#262!60?!10~$#263!70?!10~$#264!80?!10~$#265!90?!10~$#266!100?!10~$#267!110?!10~$#268!120?!10~$#269!130?!10~$#270!140?!10~$#271!150?!10~$#272!160?!10~$#273!170?!10~$#274!180?!10~$#275!190?!10~$#276!200?!10~$#277!210?!10~$#278!220?!10~$#279!230?!10~$#280!240?!10~$#281!250?!10~$#282!260?!10~$#283!270?!10~$#284!280?!10~$#285!290?!10~$#286!300?!10~$#287!310?!10~$#288!320?!10~$#289!330?!10~$#290!340?!10~$#291!350?!10~$#292!360?!10~$#293!370?!10~$#294!380?!10~$#295!390?!10~$#296!400?!10~$#297!410?!10~$#298!420?!10~$#299!430?!10~$#300!440?!10~$#301!450?!10~$#302!460?!10~$#303!470?!10~$#304!480?!10~$#305!490?!10~$#306!500?!10~$#307!510?!10~$#308!520?!10~$#309!530?!10~$#310!540?!10~$#311!550?!10~$#312!560?!10~$#313!570?!10~$#314!580?!10~$#315!590?!10~$#316!600?!10~$#317!610?!10~$#318!620?!10~$#319!630?!10~$-\n#256!10B$#320!10{$#257!10?!10B$#321!10?!10{$#258!20?!10B$#322!20?!10{$#259!30?!10B$#323!30?!10{$#260!40?!10B$#324!40?!10{$#261!50?!10B$#325!50?!10{$#262!60?!10B$#326!60?!10{$#263!70?!10B$#327!70?!10{$#264!80?!10B$#328!80?!10{$#265!90?!10B$#329!90?!10{$#266!100?!10B$#330!100?!10{$#267!110?!10B$#331!110?!10{$#268!120?!10B$#332!120?!10{$#269!130?!10B$#333!130?!10{$#270!140?!10B$#334!140?!10{$#271!150?!10B$#335!150?!10{$#272!160?!10B$#336!160?!10{$#273!170?!10B$#337!170?!10{$#274!180?!10B$#338!180?!10{$#275!190?!10B$#339!190?!10{$#276!200?!10B$#340!200?!10{$#277!210?!10B$#341!210?!10{$#278!220?!10B$#342!220?!10{$#279!230?!10B$#343!230?!10{$#280!240?!10B$#344!240?!10{$#281!250?!10B$#345!250?!10{$#282!260?!10B$#346!260?!10{$#283!270?!10B$#347!270?!10{$#284!280?!10B$#348!280?!10{$#285!290?!10B$#349!290?!10{$#286!300?!10B$#350!300?!10{$#287!310?!10B$#351!310?!10{$#288!320?!10B$#352!320?!10{$#289!330?!10B$#353!330?!10{$#290!340?!10B$#354!340?!10{$#291!350?!10B$#355!350?!10{$#292!360?!10B$#356!360?!10{$#293!370?!10B$#357!370?!10{$#294!380?!10B$#358!380?!10{$#295!390?!10B$#359!390?!10{$#296!400?!10B$#360!400?!10{$#297!410?!10B$#361!410?!10{$#298!420?!10B$#362!420?!10{$#299!430?!10B$#363!430?!10{$#300!440?!10B$#364!440?!10{$#301!450?!10B$#365!450?!10{$#302!460?!10B$#366!460?!10{$#303!470?!10B$#367!470?!10{$#304!480?!10B$#368!480?!10{$#305!490?!10B$#369!490?!10{$#306!500?!10B$#370!500?!10{$#307!510?!10B$#371!510?!10{$#308!520?!10B$#372!520?!10{$#309!530?!10B$#373!530?!10{$#310!540?!10B$#374!540?!10{$#311!550?!10B$#375!550?!10{$#312!560?!10B$#376!560?!10{$#313!570?!10B$#377!570?!10{$#314!580?!10B$#378!580?!10{$#315!590?!10B$#379!590?!10{$#316!600?!10B$#380!600?!10{$#317!610?!10B$#381!610?!10{$#318!620?!10B$#382!620?!10{$#319!630?!10B$#383!630?!10{$-\n#320!10~$#321!10?!10~$#322!20?!10~$#323!30?!10~$#324!40?!10~$#325!50?!10~$#326!60?!10~$#327!70?!10~$#328!80?!10~$#329!90?!10~$#330!100?!10~$#331!110?!10~$#332!120?!10~$#333!130?!10~$#334!140?!10~$#335!150?!10~$#336!160?!10~$#337!170?!10~$#338!180?!10~$#339!190?!10~$#340!200?!10~$#341!210?!10~$#342!220?!10~$#343!230?!10~$#344!240?!10~$#345!250?!10~$#346!260?!10~$#347!270?!10~$#348!280?!10~$#349!290?!10~$#350!300?!10~$#351!310?!10~$#352!320?!10~$#353!330?!10~$#354!340?!10~$#355!350?!10~$#356!360?!10~$#357!370?!10~$#358!380?!10~$#359!390?!10~$#360!400?!10~$#361!410?!10~$#362!420?!10~$#363!430?!10~$#364!440?!10~$#365!450?!10~$#366!460?!10~$#367!470?!10~$#368!480?!10~$#369!490?!10~$#370!500?!10~$#371!510?!10~$#372!520?!10~$#373!530?!10~$#374!540?!10~$#375!550?!10~$#376!560?!10~$#377!570?!10~$#378!580?!10~$#379!590?!10~$#380!600?!10~$#381!610?!10~$#382!620?!10~$#383!630?!10~$-\n#384!10~$#385!10?!10~$#386!20?!10~$#387!30?!10~$#388!40?!10~$#389!50?!10~$#390!60?!10~$#391!70?!10~$#392!80?!10~$#393!90?!10~$#394!100?!10~$#395!110?!10~$#396!120?!10~$#397!130?!10~$#398!140?!10~$#399!150?!10~$#400!160?!10~$#401!170?!10~$#402!180?!10~$#403!190?!10~$#404!200?!10~$#405!210?!10~$#406!220?!10~$#407!230?!10~$#408!240?!10~$#409!250?!10~$#410!260?!10~$#411!270?!10~$#412!280?!10~$#413!290?!10~$#414!300?!10~$#415!310?!10~$#416!320?!10~$#417!330?!10~$#418!340?!10~$#419!350?!10~$#420!360?!10~$#421!370?!10~$#422!380?!10~$#423!390?!10~$#424!400?!10~$#425!410?!10~$#426!420?!10~$#427!430?!10~$#428!440?!10~$#429!450?!10~$#430!460?!10~$#431!470?!10~$#432!480?!10~$#433!490?!10~$#434!500?!10~$#435!510?!10~$#436!520?!10~$#437!530?!10~$#438!540?!10~$#439!550?!10~$#440!560?!10~$#441!570?!10~$#442!580?!10~$#443!590?!10~$#444!600?!10~$#445!610?!10~$#446!620?!10~$#447!630?!10~$-\n#384!10N$#448!10o$#385!10?!10N$#449!10?!10o$#386!20?!10N$#450!20?!10o$#387!30?!10N$#451!30?!10o$#388!40?!10N$#452!40?!10o$#389!50?!10N$#453!50?!10o$#390!60?!10N$#454!60?!10o$#391!70?!10N$#455!70?!10o$#392!80?!10N$#456!80?!10o$#393!90?!10N$#457!90?!10o$#394!100?!10N$#458!100?!10o$#395!110?!10N$#459!110?!10o$#396!120?!10N$#460!120?!10o$#397!130?!10N$#461!130?!10o$#398!140?!10N$#462!140?!10o$#399!150?!10N$#463!150?!10o$#400!160?!10N$#464!160?!10o$#401!170?!10N$#465!170?!10o$#402!180?!10N$#466!180?!10o$#403!190?!10N$#467!190?!10o$#404!200?!10N$#468!200?!10o$#405!210?!10N$#469!210?!10o$#406!220?!10N$#470!220?!10o$#407!230?!10N$#471!230?!10o$#408!240?!10N$#472!240?!10o$#409!250?!10N$#473!250?!10o$#410!260?!10N$#474!260?!10o$#411!270?!10N$#475!270?!10o$#412!280?!10N$#476!280?!10o$#413!290?!10N$#477!290?!10o$#414!300?!10N$#478!300?!10o$#415!310?!10N$#479!310?!10o$#416!320?!10N$#480!320?!10o$#417!330?!10N$#481!330?!10o$#418!340?!10N$#482!340?!10o$#419!350?!10N$#483!350?!10o$#420!360?!10N$#484!360?!10o$#421!370?!10N$#485!370?!10o$#422!380?!10N$#486!380?!10o$#423!390?!10N$#487!390?!10o$#424!400?!10N$#488!400?!10o$#425!410?!10N$#489!410?!10o$#426!420?!10N$#490!420?!10o$#427!430?!10N$#491!430?!10o$#428!440?!10N$#492!440?!10o$#429!450?!10N$#493!450?!10o$#430!460?!10N$#494!460?!10o$#431!470?!10N$#495!470?!10o$#432!480?!10N$#496!480?!10o$#433!490?!10N$#497!490?!10o$#434!500?!10N$#498!500?!10o$#435!510?!10N$#499!510?!10o$#436!520?!10N$#500!520?!10o$#437!530?!10N$#501!530?!10o$#438!540?!10N$#502!540?!10o$#439!550?!10N$#503!550?!10o$#440!560?!10N$#504!560?!10o$#441!570?!10N$#505!570?!10o$#442!580?!10N$#506!580?!10o$#443!590?!10N$#507!590?!10o$#444!600?!10N$#508!600?!10o$#445!610?!10N$#509!610?!10o$#446!620?!10N$#510!620?!10o$#447!630?!10N$#511!630?!10o$-\n#448!10~$#449!10?!10~$#450!20?!10~$#451!30?!10~$#452!40?!10~$#453!50?!10~$#454!60?!10~$#455!70?!10~$#456!80?!10~$#457!90?!10~$#458!100?!10~$#459!110?!10~$#460!120?!10~$#461!130?!10~$#462!140?!10~$#463!150?!10~$#464!160?!10~$#465!170?!10~$#466!180?!10~$#467!190?!10~$#468!200?!10~$#469!210?!10~$#470!220?!10~$#471!230?!10~$#472!240?!10~$#473!250?!10~$#474!260?!10~$#475!270?!10~$#476!280?!10~$#477!290?!10~$#478!300?!10~$#479!310?!10~$#480!320?!10~$#481!330?!10~$#482!340?!10~$#483!350?!10~$#484!360?!10~$#485!370?!10~$#486!380?!10~$#487!390?!10~$#488!400?!10~$#489!410?!10~$#490!420?!10~$#491!430?!10~$#492!440?!10~$#493!450?!10~$#494!460?!10~$#495!470?!10~$#496!480?!10~$#497!490?!10~$#498!500?!10~$#499!510?!10~$#500!520?!10~$#501!530?!10~$#502!540?!10~$#503!550?!10~$#504!560?!10~$#505!570?!10~$#506!580?!10~$#507!590?!10~$#508!600?!10~$#509!610?!10~$#510!620?!10~$#511!630?!10~$-\n#448!10B$#449!10?!10B$#450!20?!10B$#451!30?!10B$#452!40?!10B$#453!50?!10B$#454!60?!10B$#455!70?!10B$#456!80?!10B$#457!90?!10B$#458!100?!10B$#459!110?!10B$#460!120?!10B$#461!130?!10B$#462!140?!10B$#463!150?!10B$#464!160?!10B$#465!170?!10B$#466!180?!10B$#467!190?!10B$#468!200?!10B$#469!210?!10B$#470!220?!10B$#471!230?!10B$#472!240?!10B$#473!250?!10B$#474!260?!10B$#475!270?!10B$#476!280?!10B$#477!290?!10B$#478!300?!10B$#479!310?!10B$#480!320?!10B$#481!330?!10B$#482!340?!10B$#483!350?!10B$#484!360?!10B$#485!370?!10B$#486!380?!10B$#487!390?!10B$#488!400?!10B$#489!410?!10B$#490!420?!10B$#491!430?!10B$#492!440?!10B$#493!450?!10B$#494!460?!10B$#495!470?!10B$#496!480?!10B$#497!490?!10B$#498!500?!10B$#499!510?!10B$#500!520?!10B$#501!530?!10B$#502!540?!10B$#503!550?!10B$#504!560?!10B$#505!570?!10B$#506!580?!10B$#507!590?!10B$#508!600?!10B$#509!610?!10B$#510!620?!10B$#511!630?!10B$\u001b\\"
  },
  {
    "path": "addons/addon-image/fixture/textcursor.sh",
    "content": "#!/bin/bash\n\n# Test cursor row, column placement after sixel image is sent.\n\n# After a sixel image is displayed, the text cursor is moved to the\n# row of the last sixel cursor position, but the column stays the same\n# as it was before the sixel image was sent.\n#\n# This can be thought of as sixel images always ending with an\n# implicit Graphics Carriage Return (`$`). \n\n# ADDENDUM: It is not as simple as I thought. When a row of sixels\n# straddles two rows of text, the text cursor can be left on the upper row.\n# It seems up to three lines of pixels may be beneath any words printed.\n#\n# The rule for when this happens is not obvious to me, but can be seen\n# with images of height: 21, 22, 23, 24, 41, 42, 81, 82, 83, 84...\n#\n# My guess:\n# for a sixel image of height h, let a=(h-1)%6 and b=(h-1)%20,\n# then, the text will overlap the image when a>b.\n#\n# If that is the case, then the entire list of heights for which this\n# will happen on the VT340's 480 pixel high screen is:\n#\n#  21  22  23  24   41  42   81  82  83  84\n# 101 102  141 142 143 144  161 162 \n# 201 202 203 204  221 222  261 262 263 264  281 282 \n# 321 322 323 324  341 342  381 382 383 384\n# 401 402  441 442 443 444  461 462\n#\n# Note that there are 48 entries, so that means there's a 10% chance\n# if heights are chosen randomly from 1 to 480. However, if one were\n# to always pick heights which are a multiple of the character cell\n# height (20px), then the chances are 0% as there are no problematic\n# heights divisible by 20. \n\n\n# Sixel images often do *not* end with a `-` (Graphics New Line = GNL)\n# which sends the sixel cursor down 6 pixels. Any text printed next\n# will potentially overlap the last row of sixels!\n\n# I am not yet positive, but I believe that, in general, applications\n# should send sixel images without a GNL but then send `^J`, a text\n# newline (NL), before displaying more text or graphics.\n\n# IMPORTANT: sometimes neither a graphics nor a text newline is wanted. \n# For example, if an image is full screen, either newline would cause\n# the top line to scroll off the screen.\n\n#         | Text cursor column | Text cursor row\n# --------|--------------------|-------------------------------------\n# !GNL !NL| Unchanged\t       | Overlapping last line of graphics\n# !GNL  NL| Column=1\t       | First line immediately after graphic (usually)\n#  GNL !NL| Unchanged\t       | _Sometimes_ overlapping graphics\n#  GNL  NL| Column=1\t       | First *or* second line after graphic\n\n\nCSI=$'\\e['\t\t\t# Control Sequence Introducer \nDCS=$'\\eP'\t\t\t# Device Control String\nST=$'\\e\\\\'\t\t\t# String Terminator\n\nset_cursor_pos() {\n    # Home, top left is row 1, col 1.\n    local row=$1 col=$2\n    echo -n ${CSI}${row}';'${col}'H'\n}\n\nreset_palette() {\n    # Send DECRSTS to load colors from a Color Table Report\n    echo -n ${DCS}'2$p'\n\n    echo -n \"0;2;0;0;0/\"        # VT color #0 is black and BG text color\n\n    echo -n \"1;2;20;20;79/\"\t# VT color #1 is blue\n    echo -n \"2;2;79;13;13/\"\t# VT color #2 is red\n    echo -n \"3;2;20;79;20/\"\t# VT color #3 is green\n\n    echo -n \"4;2;79;20;79/\"\t# VT color #4 is magenta\n    echo -n \"5;2;20;79;79/\"\t# VT color #5 is cyan\n    echo -n \"6;2;79;79;20/\"\t# VT color #6 is yellow\n\n    echo -n \"7;2;46;46;46/\"\t# VT color #7 is gray 50% and FG text color\n    echo -n \"8;2;26;26;26/\"\t# VT color #8 is gray 25%\n\n    echo -n \"9;2;33;33;59/\"\t# VT color #9 is pastel blue\n    echo -n \"10;2;59;26;26/\"\t# VT color #10 is pastel red\n    echo -n \"11;2;33;59;33/\"\t# VT color #11 is pastel green\n\n    echo -n \"12;2;59;33;59/\"\t# VT color #12 is pastel magenta\n    echo -n \"13;2;33;59;59/\"\t# VT color #13 is pastel cyan\n    echo -n \"14;2;59;59;33/\"\t# VT color #14 is pastel yellow\n\n    echo -n \"15;2;79;79;79\"\t# VT color #15 is gray 75% and BOLD text color\n\n    echo -n ${ST}\t\t# String Terminator\n}\n\n# Generate square of size w with final graphics new line removed\nsquare() {\n    # Given a color index number and (optionally) a size, row, and column, \n    # draw a square with top left corner at (row, column) and of size×size px.\n    # Default size 100×100px  (10cols, 5 rows)\n\n    local -i color=${1:-1}\t# Default is color index 1 (blue)\n    local -i size=${2:-100}\t# Size in pixels (defaults to 100)\n    local -i row=$3 column=$4\t# If set to 0, cursor is not moved\n\n    if [[ row -ne 0 && column -ne 0 ]]; then\n\tset_cursor_pos $row $column\n    fi\n\n    # Draw a square of the right color & size \n    squaresize $color $size\n}\n\nsquaresize() {\n    # Helper for square() that uses convert to return a  sixel square of\n    # the right color ($1) and size ($2).\n\n    # Similar to this but with variable size squares:\n    #    echo -n ${DCS}'0;0;0q\"1;1;100;100#'${color}'!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100N'${ST}\n\n\n    local color=${1:-1}\t\t\t# Default color index is 1 (blue)\n    local size=${2:-100}\t\t# Default size is 100x100\n\n    # Get a sixel string \n    local sq=$(convert -geometry ${size}x${size} xc:black sixel:-)\n\n    # Remove ImageMagick's extraneous Graphic New Line at end of image.\n    sq=${sq%-??}$'\\e\\\\'\n\n    # VT340s always used the same color register for the first sixel\n    # color defined no matter what number it was assigned. That means,\n    # each time we send a new sixel image, the previous one's color\n    # palette gets changed. We don't want squares of all the same\n    # color, so remove the color definition and just use the defaults.\n    sq=${sq/\\#0;2;0;0;0/}\n\n    # And finally, switch to the proper index for the color we want.\n    echo -n ${sq//\\#0/#${color}}\n}\n\nsquaregnl() {\n    # Same as square(), but sends a graphics newline at the end of the sixels.\n    # (Sticks a `-` before the String Terminator, \"Esc \\\")\n    sq=$(square \"${@}\")\n    echo -n ${sq%??}$'-\\e\\\\'\n}\n\n\nmain() {\n    clear\n    reset_palette\n    show_labels\n    neither_graphic_nor_text 96 4 4 31 \t\t# size, color, row, column\n    text_newline_only 96 9 4 1\n    text_newline_only 84 9 4 14\n    graphics_new_line_only 100 1 4 51\n    graphics_new_line_only 96 1 4 64\n    set_cursor_pos 1000 1\n}\n\n\nneither_graphic_nor_text() {\n    # Typically sixel images should not end with a Graphics New Line (GNL)\n    # However, if a text newline isn't sent, there will be overlap.\n\n    local -i size  color  row  column\n    read size color row column <<<\"$@\"\n\n    set_cursor_pos $((row++)) $column\n    echo -n \"Height $size\"\n    set_cursor_pos $((row++)) $column\n\n    # Three squares sent as separate sixel images, indented +1 \n    for i in {1..3}; do\n\tsquare $((color++)) $size\n\ttput cuf 1\n    done\n\n    echo -n \"overlap?\"\n}\n\n\ntext_newline_only() {\n    # USING A TEXT NEWLINE (NL) after an sixel image that does NOT\n    # have GNL is probably the best way to be on the text line\n    # immediately below the image. However, the text will still\n    # occasionally overlap the last four rows of pixels.\n\n    # Also, if multiple images are intended to be shown, there will\n    # usually be a gap between them when using a text newline.\n\n    # Overlap happens because the height of a text cell is 20 pixels\n    # and the height of a sixel is 6. \n    # for (h=0; h<480; h++) if ((h-1)%6 > (h-1)%20 ) { h }\n\n    # Let the pixel position of the top of the graphics cursor be 'Yg'\n    # and let the pixel position of the top of the corresponding cell\n    # of text which the text cursor will be placed on be 'Yt'. Note\n    # that Yg is evenly divisible by 6 and Yt, by 20. Taking the\n    # remainder, r, after dividing Yg by 20 tells us how many pixels\n    # down into a row of text the last line of sixels started. When\n    # r==0, the sixels started at the top of the text row.\n\n    # When r = 14, the sixels covered the bottom six pixels on the row\n    # of text. When 14 < r < 20, the sixel line impinged by r - 14\n    # pixels into the text row below and there is a chance the next\n    # text printed will overlap. \n\n\n    local -i size  color  row  column\n    read size color row column <<<\"$@\"\n\n    local -i offset\n    offset=$((column-1))\n\n    set_cursor_pos $row 1\n\n    if ((offset)); then tput cuf $((offset)); fi\n    echo \"Height $size\"\n\n    # Three squares, separated by text new lines and indented +1 \n    for i in {1..3}; do\n\tif ((offset)); then tput cuf $offset; fi\n\tsquare $((color++)) $size\n\toffset=offset+1\n\techo\n    done\n\n    tput cuf $((offset))\n    echo -n \"overlap?\"\n}\n\ngraphics_new_line_only() {\n    # However, some sixel images end with a `-`, a Graphics New Line.\n    # This can be useful for writing another image starting at the same\n    # column without having to reposition the cursor.\n    #\n    # However, this runs the risk of having occasional overlap.\n\n    local -i size  color  row  column\n    read size color row column <<<\"$@\"\n\n    set_cursor_pos $((row++)) $column\n    echo -n \"Height $size\"\n    set_cursor_pos $((row++)) $column\n\n    # Three squares, separated by graphics new lines and indented +1 \n    for i in {1..3}; do\n\tsquaregnl $((color++))  $size\n\ttput cuf 1\n    done\n\n    echo -n \"overlap?\"\n}\n\n\nshow_labels() {\n    set_cursor_pos 1 10\n    echo -n \"Should sixel images include a GNL ('-') at the end?\"\n\n    set_cursor_pos 3 29\n    echo -n \"Neither NL nor GNL\"\n    set_cursor_pos 3 1\n    echo -n \"Text New Line only\"\n    set_cursor_pos 3 51\n    echo -n \"Graphics New Line only\"\n\n    set_cursor_pos 22 29\n    echo -n \"Always overlaps\"\t\t# Neither NL nor GNL\n\n    set_cursor_pos 22 3\n    echo -n \"Overlaps a little\"\t\t# NL only\n    set_cursor_pos 23 3\n    echo -n \"    Gaps a little\"\t\t# NL only\n\n    set_cursor_pos 22 54\n    echo -n \"Overlaps badly\"\t\t# GNL only\n    set_cursor_pos 23 54\n    echo -n \"Never gaps\"\t\t# GNL only\n}\n\n\nmain\n\n"
  },
  {
    "path": "addons/addon-image/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-image\",\n  \"version\": \"0.9.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-image.js\",\n  \"module\": \"lib/addon-image.mjs\",\n  \"types\": \"typings/addon-image.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"image\",\n    \"sixel\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"prepackage\": \"../../node_modules/.bin/tsgo -p .\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  },\n  \"devDependencies\": {\n    \"sixel\": \"^0.16.0\",\n    \"xterm-wasm-parts\": \"^0.3.0\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/IIPHandler.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types';\nimport { ImageRenderer } from './ImageRenderer';\nimport { IIPImageStorage } from './IIPImageStorage';\nimport { CELL_SIZE_DEFAULT } from './ImageStorage';\nimport Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';\nimport { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';\nimport { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';\n\n// Local const enum mirror - esbuild can't inline const enums from external packages\nconst enum DecoderConst {\n  // Limit held memory in base64 decoder (encoded bytes).\n  KEEP_DATA = 4194304,\n  // Initial buffer allocation for the decoder.\n  INITIAL_DATA = 1048576,\n  // Local mirror of const enum (esbuild can't inline const enums from external packages)\n  OK = 0\n}\n\n// default IIP header values\nconst DEFAULT_HEADER: IHeaderFields = {\n  name: 'Unnamed file',\n  size: 0,\n  width: 'auto',\n  height: 'auto',\n  preserveAspectRatio: 1,\n  inline: 0\n};\n\n\nexport class IIPHandler implements IOscHandler, IResetHandler {\n  private _aborted = false;\n  private _hp = new HeaderParser();\n  private _header: IHeaderFields = DEFAULT_HEADER;\n  private _dec: Base64Decoder;\n  private _metrics = UNSUPPORTED_TYPE;\n\n  constructor(\n    private readonly _opts: IImageAddonOptions,\n    private readonly _renderer: ImageRenderer,\n    private readonly _storage: IIPImageStorage,\n    private readonly _coreTerminal: ITerminalExt\n  ) {\n    const maxEncodedBytes = Math.ceil(this._opts.iipSizeLimit * 4 / 3);\n    const initialBytes = Math.min(DecoderConst.INITIAL_DATA, maxEncodedBytes);\n    this._dec = new Base64Decoder(DecoderConst.KEEP_DATA, maxEncodedBytes, initialBytes);\n  }\n\n  public reset(): void {}\n\n  public start(): void {\n    this._aborted = false;\n    this._header = DEFAULT_HEADER;\n    this._metrics  = UNSUPPORTED_TYPE;\n    this._hp.reset();\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._aborted) return;\n\n    if (this._hp.state === HeaderState.END) {\n      if ((this._dec.put(data.subarray(start, end)) as number) !== DecoderConst.OK) {\n        this._dec.release();\n        this._aborted = true;\n      }\n    } else {\n      const dataPos = this._hp.parse(data, start, end);\n      if (dataPos === -1) {\n        this._aborted = true;\n        return;\n      }\n      if (dataPos > 0) {\n        this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);\n        if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {\n          this._aborted = true;\n          return;\n        }\n        this._dec.init();\n        if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {\n          this._dec.release();\n          this._aborted = true;\n        }\n      }\n    }\n  }\n\n  public end(success: boolean): boolean | Promise<boolean> {\n    if (this._aborted) return true;\n\n    let w = 0;\n    let h = 0;\n\n    // early exit condition chain\n    let cond: number | boolean = true;\n    if (cond = success) {\n      if (cond = !this._dec.end()) {\n        if (cond = this._dec.data8.length === this._header.size) {\n          this._metrics = imageType(this._dec.data8);\n          if (cond = this._metrics.mime !== 'unsupported') {\n            w = this._metrics.width;\n            h = this._metrics.height;\n            if (cond = w && h && w * h < this._opts.pixelLimit) {\n              [w, h] = this._resize(w, h).map(Math.floor);\n              cond = w && h && w * h < this._opts.pixelLimit;\n            }\n          }\n        }\n      }\n    }\n    if (!cond) {\n      this._dec.release();\n      return true;\n    }\n\n    // HACK: The types on Blob are too restrictive, this is a Uint8Array so the browser accepts it\n    const blob = new Blob([this._dec.data8 as Uint8Array<ArrayBuffer>], { type: this._metrics.mime });\n    this._dec.release();\n\n    if (!window.createImageBitmap) {\n      const url = URL.createObjectURL(blob);\n      const img = new Image();\n      return new Promise<boolean>(r => {\n        img.addEventListener('load', () => {\n          URL.revokeObjectURL(url);\n          const canvas = ImageRenderer.createCanvas(window.document, w, h);\n          canvas.getContext('2d')?.drawImage(img, 0, 0, w, h);\n          this._storage.addImage(canvas);\n          r(true);\n        });\n        img.src = url;\n        // sanity measure to avoid terminal blocking from dangling promise\n        // happens from corrupt data (onload never gets fired)\n        setTimeout(() => r(true), 1000);\n      });\n    }\n    return createImageBitmap(blob, { resizeWidth: w, resizeHeight: h })\n      .then(bm => {\n        this._storage.addImage(bm);\n        return true;\n      });\n  }\n\n  private _resize(w: number, h: number): [number, number] {\n    const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;\n    const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;\n    const width = this._renderer.dimensions?.css.canvas.width || cw * this._coreTerminal.cols;\n    const height = this._renderer.dimensions?.css.canvas.height || ch * this._coreTerminal.rows;\n\n    const rw = this._dim(this._header.width!, width, cw);\n    const rh = this._dim(this._header.height!, height, ch);\n    if (!rw && !rh) {\n      const wf = width / w;         // TODO: should this respect initial cursor offset?\n      const hf = (height - ch) / h; // TODO: fix offset issues from float cell height\n      const f = Math.min(wf, hf);\n      return f < 1 ? [w * f, h * f] : [w, h];\n    }\n    return !rw\n      ? [w * rh / h, rh]\n      : this._header.preserveAspectRatio || !rw || !rh\n        ? [rw, h * rw / w] : [rw, rh];\n  }\n\n  private _dim(s: string, total: number, cdim: number): number {\n    if (s === 'auto') return 0;\n    if (s.endsWith('%')) return parseInt(s.slice(0, -1)) * total / 100;\n    if (s.endsWith('px')) return parseInt(s.slice(0, -2));\n    return parseInt(s) * cdim;\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/IIPHeaderParser.test.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';\n\n\nconst CASES: [string, IHeaderFields][] = [\n  ['File=size=123456;name=dGVzdA==:', {name: 'test', size: 123456}],\n  ['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],\n  // utf-8 encoding in name\n  ['File=size=123456;name=w7xtbMOkdXTDnw==:', {name: 'ümläutß', size: 123456}],\n  ['File=size=123456;name=w7xtbMOkdXTDnw:', {name: 'ümläutß', size: 123456}],\n  // full header spec\n  [\n    'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',\n    {\n      inline: 1,\n      width: '10px',\n      height: '20%',\n      preserveAspectRatio: 1,\n      size: 123456,\n      name: 'ümläutß'\n    }\n  ],\n  [\n    'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',\n    {\n      inline: 1,\n      width: 'auto',\n      height: '20',\n      preserveAspectRatio: 1,\n      size: 123456,\n      name: 'ümläutß'\n    }\n  ]\n];\n\nfunction fromBs(bs: string): Uint32Array {\n  const r = new Uint32Array(bs.length);\n  for (let i = 0; i < r.length; ++i) r[i] = bs.charCodeAt(i);\n  return r;\n}\n\ndescribe('IIPHeaderParser', () => {\n  it('at once', () => {\n    const hp = new HeaderParser();\n    for (const example of CASES) {\n      hp.reset();\n      const inp = fromBs(example[0]);\n      const res = hp.parse(inp, 0, inp.length);\n      assert.strictEqual(res, inp.length);\n      assert.strictEqual(hp.state, HeaderState.END);\n      assert.deepEqual(hp.fields, example[1]);\n    }\n  });\n  it('bytewise', () => {\n    const hp = new HeaderParser();\n    for (const example of CASES) {\n      hp.reset();\n      const inp = fromBs(example[0]);\n      let pos = 0;\n      let res = -2;\n      while (res === -2 && pos < inp.length) {\n        res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);\n      }\n      assert.strictEqual(res, 1);\n      assert.strictEqual(hp.state, HeaderState.END);\n      assert.deepEqual(hp.fields, example[1]);\n    }\n  });\n  it('no File= starter', () => {\n    const hp = new HeaderParser();\n    let inp = fromBs('size=123456;name=dGVzdA==:');\n    let res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, -1);\n    hp.reset();\n    inp = fromBs(CASES[0][0]);\n    res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, CASES[0][1]);\n  });\n  it('empty key - error', () => {\n    const hp = new HeaderParser();\n    let inp = fromBs('File=size=123456;=dGVzdA==:');\n    let res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, -1);\n    hp.reset();\n    inp = fromBs(CASES[0][0]);\n    res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, CASES[0][1]);\n  });\n  it('empty size value - set to 0', () => {\n    const hp = new HeaderParser();\n    let inp = fromBs('File=size=;name=dGVzdA==:');\n    let res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, {name: 'test', size: 0});\n    hp.reset();\n    inp = fromBs(CASES[0][0]);\n    res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, CASES[0][1]);\n  });\n  it('empty name value - set to empty string', () => {\n    const hp = new HeaderParser();\n    let inp = fromBs('File=size=123456;name=:');\n    let res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, {name: '', size: 123456});\n    hp.reset();\n    inp = fromBs(CASES[0][0]);\n    res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, CASES[0][1]);\n  });\n  it('empty size value - error', () => {\n    const hp = new HeaderParser();\n    let inp = fromBs('File=inline=1;width=;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:');\n    let res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, -1);\n    hp.reset();\n    inp = fromBs(CASES[0][0]);\n    res = hp.parse(inp, 0, inp.length);\n    assert.strictEqual(res, inp.length);\n    assert.strictEqual(hp.state, HeaderState.END);\n    assert.deepEqual(hp.fields, CASES[0][1]);\n  });\n});\n"
  },
  {
    "path": "addons/addon-image/src/IIPHeaderParser.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// eslint-disable-next-line\ndeclare const Buffer: any;\n\n\nexport interface IHeaderFields {\n  [key: string]: number | string | Uint32Array | null | undefined;\n  // base-64 encoded filename. Defaults to \"Unnamed file\".\n  name: string;\n  // File size in bytes. The file transfer will be canceled if this size is exceeded.\n  size: number;\n  /**\n   * Optional width and height to render:\n   * - N: N character cells.\n   * - Npx: N pixels.\n   * - N%: N percent of the session's width or height.\n   * - auto: The image's inherent size will be used to determine an appropriate dimension.\n   */\n  width?: string;\n  height?: string;\n  // Optional, defaults to 1 respecting aspect ratio (width takes precedence).\n  preserveAspectRatio?: number;\n  // Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded\n  // (download not supported).\n  inline?: number;\n}\n\nexport const enum HeaderState {\n  START = 0,\n  ABORT = 1,\n  KEY = 2,\n  VALUE = 3,\n  END = 4\n}\n\n// field value decoders\n\n// ASCII bytes to string\nfunction toStr(data: Uint32Array): string {\n  let s = '';\n  for (let i = 0; i < data.length; ++i) {\n    s += String.fromCharCode(data[i]);\n  }\n  return s;\n}\n\n// digits to integer\nfunction toInt(data: Uint32Array): number {\n  let v = 0;\n  for (let i = 0; i < data.length; ++i) {\n    if (data[i] < 48 || data[i] > 57) {\n      throw new Error('illegal char');\n    }\n    v = v * 10 + data[i] - 48;\n  }\n  return v;\n}\n\n// check for correct size entry\nfunction toSize(data: Uint32Array): string {\n  const v = toStr(data);\n  if (!v.match(/^((auto)|(\\d+?((px)|(%)){0,1}))$/)) {\n    throw new Error('illegal size');\n  }\n  return v;\n}\n\n// name is base64 encoded utf-8\nfunction toName(data: Uint32Array): string {\n  if (typeof Buffer !== 'undefined') {\n    return Buffer.from(toStr(data), 'base64').toString();\n  }\n  const bs = atob(toStr(data));\n  const b = new Uint8Array(bs.length);\n  for (let i = 0; i < b.length; ++i) {\n    b[i] = bs.charCodeAt(i);\n  }\n  return new TextDecoder().decode(b);\n}\n\nconst DECODERS: {[key: string]: (v: Uint32Array) => number | string} = {\n  inline: toInt,\n  size: toInt,\n  name: toName,\n  width: toSize,\n  height: toSize,\n  preserveAspectRatio: toInt\n};\n\n\nconst FILE_MARKER = [70, 105, 108, 101];\nconst MAX_FIELDCHARS = 1024;\n\n\nexport class HeaderParser {\n  public state: HeaderState = HeaderState.START;\n  private _buffer = new Uint32Array(MAX_FIELDCHARS);\n  private _position = 0;\n  private _key = '';\n  public fields: {[key: string]: number | string | Uint32Array | null | undefined} = {};\n\n  public reset(): void {\n    this._buffer.fill(0);\n    this.state = HeaderState.START;\n    this._position = 0;\n    this.fields = {};\n    this._key = '';\n  }\n\n  public parse(data: Uint32Array, start: number, end: number): number {\n    let state = this.state;\n    let pos = this._position;\n    const buffer = this._buffer;\n    if (state === HeaderState.ABORT || state === HeaderState.END) return -1;\n    if (state === HeaderState.START && pos > 6) return -1;\n    for (let i = start; i < end; ++i) {\n      const c = data[i];\n      switch (c) {\n        case 59: // ;\n          if (!this._storeValue(pos)) return this._a();\n          state = HeaderState.KEY;\n          pos = 0;\n          break;\n        case 61: // =\n          if (state === HeaderState.START) {\n            for (let k = 0; k < FILE_MARKER.length; ++k) {\n              if (buffer[k] !== FILE_MARKER[k]) return this._a();\n            }\n            state = HeaderState.KEY;\n            pos = 0;\n          } else if (state === HeaderState.KEY) {\n            if (!this._storeKey(pos)) return this._a();\n            state = HeaderState.VALUE;\n            pos = 0;\n          } else if (state === HeaderState.VALUE) {\n            if (pos >= MAX_FIELDCHARS) return this._a();\n            buffer[pos++] = c;\n          }\n          break;\n        case 58: // :\n          if (state === HeaderState.VALUE) {\n            if (!this._storeValue(pos)) return this._a();\n          }\n          this.state = HeaderState.END;\n          return i + 1;\n        default:\n          if (pos >= MAX_FIELDCHARS) return this._a();\n          buffer[pos++] = c;\n      }\n    }\n    this.state = state;\n    this._position = pos;\n    return -2;\n  }\n\n  private _a(): number {\n    this.state = HeaderState.ABORT;\n    return -1;\n  }\n\n  private _storeKey(pos: number): boolean {\n    const k = toStr(this._buffer.subarray(0, pos));\n    if (k) {\n      this._key = k;\n      this.fields[k] = null;\n      return true;\n    }\n    return false;\n  }\n\n  private _storeValue(pos: number): boolean {\n    if (this._key) {\n      try {\n        const v = this._buffer.slice(0, pos);\n        this.fields[this._key] = DECODERS[this._key] ? DECODERS[this._key](v) : v;\n      } catch {\n        return false;\n      }\n      return true;\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/IIPImageStorage.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ImageStorage } from './ImageStorage';\n\n/**\n * IIP (iTerm Image Protocol) specific image storage controller.\n *\n * Wraps the shared ImageStorage with IIP protocol semantics:\n * - Always uses scrolling mode (cursor advances with image)\n */\nexport class IIPImageStorage {\n  constructor(\n    private readonly _storage: ImageStorage\n  ) {}\n\n  /**\n   * Add an IIP image to storage.\n   * Always uses scrolling mode — cursor advances past the image.\n   */\n  public addImage(img: HTMLCanvasElement | ImageBitmap): void {\n    this._storage.addImage(img, true);\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/IIPMetrics.test.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { imageType, IMetrics } from './IIPMetrics';\n\n// fix missing nodejs decl\ndeclare const require: (s: string) => any;\nconst fs = require('fs');\n\n\nconst TEST_IMAGES: [string, IMetrics][] = [\n  ['w3c_home_256.gif', { mime: 'image/gif', width: 72, height: 48 }],\n  ['w3c_home_256.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],\n  ['w3c_home_256.png', { mime: 'image/png', width: 72, height: 48 }],\n  ['w3c_home_2.gif', { mime: 'image/gif', width: 72, height: 48 }],\n  ['w3c_home_2.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],\n  ['w3c_home_2.png', { mime: 'image/png', width: 72, height: 48 }],\n  ['w3c_home_animation.gif', { mime: 'image/gif', width: 72, height: 48 }],\n  ['w3c_home.gif', { mime: 'image/gif', width: 72, height: 48 }],\n  ['w3c_home_gray.gif', { mime: 'image/gif', width: 72, height: 48 }],\n  ['w3c_home_gray.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],\n  ['w3c_home_gray.png', { mime: 'image/png', width: 72, height: 48 }],\n  ['w3c_home.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],\n  ['w3c_home.png', { mime: 'image/png', width: 72, height: 48 }],\n  ['w3c_home_noexif.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],\n  ['spinfox.png', { mime: 'image/png', width: 148, height: 148 }],\n  ['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],\n  ['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],\n  ['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],\n  ['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]\n];\n\n\ndescribe('IIPMetrics', () => {\n  it('bunch of testimages', () => {\n    for (let i = 0; i < TEST_IMAGES.length; ++i) {\n      const imageData = fs.readFileSync('./addons/addon-image/fixture/testimages/' + TEST_IMAGES[i][0]);\n      assert.deepStrictEqual(imageType(imageData), TEST_IMAGES[i][1]);\n    }\n  });\n});\n"
  },
  {
    "path": "addons/addon-image/src/IIPMetrics.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n\nexport type ImageType = 'image/png' | 'image/jpeg' | 'image/gif' | 'unsupported' | '';\n\nexport interface IMetrics {\n  mime: ImageType;\n  width: number;\n  height: number;\n}\n\nexport const UNSUPPORTED_TYPE: IMetrics = {\n  mime: 'unsupported',\n  width: 0,\n  height: 0\n};\n\nexport function imageType(d: Uint8Array): IMetrics {\n  if (d.length < 24) {\n    return UNSUPPORTED_TYPE;\n  }\n  const d32 = new Uint32Array(d.buffer, d.byteOffset, 6);\n  // PNG: 89 50 4E 47 0D 0A 1A 0A (8 first bytes == magic number for PNG)\n  // + first chunk must be IHDR\n  if (d32[0] === 0x474E5089 && d32[1] === 0x0A1A0A0D && d32[3] === 0x52444849) {\n    return {\n      mime: 'image/png',\n      width: d[16] << 24 | d[17] << 16 | d[18] << 8 | d[19],\n      height: d[20] << 24 | d[21] << 16 | d[22] << 8 | d[23]\n    };\n  }\n  // JPEG: FF D8 FF\n  if (d[0] === 0xFF && d[1] === 0xD8 && d[2] === 0xFF) {\n    const [width, height] = jpgSize(d);\n    return { mime: 'image/jpeg', width, height };\n  }\n  // GIF: GIF87a or GIF89a\n  if (d32[0] === 0x38464947 && (d[4] === 0x37 || d[4] === 0x39) && d[5] === 0x61) {\n    return {\n      mime: 'image/gif',\n      width: d[7] << 8 | d[6],\n      height: d[9] << 8 | d[8]\n    };\n  }\n  return UNSUPPORTED_TYPE;\n}\n\nfunction jpgSize(d: Uint8Array): [number, number] {\n  const len = d.length;\n  let i = 4;\n  let blockLength = d[i] << 8 | d[i + 1];\n  while (true) {\n    i += blockLength;\n    if (i >= len) {\n      // exhausted without size info\n      return [0, 0];\n    }\n    if (d[i] !== 0xFF) {\n      return [0, 0];\n    }\n    if (d[i + 1] === 0xC0 || d[i + 1] === 0xC2) {\n      if (i + 8 < len) {\n        return [\n          d[i + 7] << 8 | d[i + 8],\n          d[i + 5] << 8 | d[i + 6]\n        ];\n      }\n      return [0, 0];\n    }\n    i += 2;\n    blockLength = d[i] << 8 | d[i + 1];\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/ImageAddon.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { ITerminalAddon, IDisposable } from '@xterm/xterm';\nimport type { ImageAddon as IImageApi } from '@xterm/addon-image';\nimport { Emitter, type IEvent } from 'common/Event';\nimport { IIPHandler } from './IIPHandler';\nimport { ImageRenderer } from './ImageRenderer';\nimport { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';\nimport { KittyGraphicsHandler } from './kitty/KittyGraphicsHandler';\nimport { KittyImageStorage } from './kitty/KittyImageStorage';\nimport { SixelHandler } from './SixelHandler';\nimport { SixelImageStorage } from './SixelImageStorage';\nimport { IIPImageStorage } from './IIPImageStorage';\nimport { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types';\n\n// default values of addon ctor options\nconst DEFAULT_OPTIONS: IImageAddonOptions = {\n  enableSizeReports: true,\n  pixelLimit: 16777216, // limit to 4096 * 4096 pixels\n  sixelSupport: true,\n  sixelScrolling: true,\n  sixelPaletteLimit: 256,\n  sixelSizeLimit: 25000000,\n  storageLimit: 128,\n  showPlaceholder: true,\n  iipSupport: true,\n  iipSizeLimit: 20000000,\n  kittySupport: true,\n  kittySizeLimit: 20000000\n};\n\n// max palette size supported by the sixel lib (compile time setting)\nconst MAX_SIXEL_PALETTE_SIZE = 4096;\n\n// definitions for _xtermGraphicsAttributes sequence\nconst enum GaItem {\n  COLORS = 1,\n  SIXEL_GEO = 2,\n  REGIS_GEO = 3\n}\nconst enum GaAction {\n  READ = 1,\n  SET_DEFAULT = 2,\n  SET = 3,\n  READ_MAX = 4\n}\nconst enum GaStatus {\n  SUCCESS = 0,\n  ITEM_ERROR = 1,\n  ACTION_ERROR = 2,\n  FAILURE = 3\n}\n\n\nexport class ImageAddon implements ITerminalAddon, IImageApi {\n  private _opts: IImageAddonOptions;\n  private _defaultOpts: IImageAddonOptions;\n  private _storage: ImageStorage | undefined;\n  private _renderer: ImageRenderer | undefined;\n  private _disposables: IDisposable[] = [];\n  private _terminal: ITerminalExt | undefined;\n  private _handlers: Map<String, IResetHandler> = new Map();\n  private readonly _onImageAdded = new Emitter<void>();\n  public readonly onImageAdded: IEvent<void> = this._onImageAdded.event;\n\n  constructor(opts?: Partial<IImageAddonOptions>) {\n    this._opts = Object.assign({}, DEFAULT_OPTIONS, opts);\n    this._defaultOpts = Object.assign({}, DEFAULT_OPTIONS, opts);\n  }\n\n  public dispose(): void {\n    for (const obj of this._disposables) {\n      obj.dispose();\n    }\n    this._disposables.length = 0;\n    this._handlers.clear();\n    this._onImageAdded.dispose();\n  }\n\n  private _disposeLater(...args: IDisposable[]): void {\n    for (const obj of args) {\n      this._disposables.push(obj);\n    }\n  }\n\n  public activate(terminal: ITerminalExt): void {\n    this._terminal = terminal;\n\n    // internal data structures\n    this._renderer = new ImageRenderer(terminal);\n    this._storage = new ImageStorage(terminal, this._renderer, this._opts);\n    this._storage.onImageAdded = () => this._onImageAdded.fire();\n\n    // enable size reports\n    if (this._opts.enableSizeReports) {\n      // const windowOptions = terminal.getOption('windowOptions');\n      // windowOptions.getWinSizePixels = true;\n      // windowOptions.getCellSizePixels = true;\n      // windowOptions.getWinSizeChars = true;\n      // terminal.setOption('windowOptions', windowOptions);\n      const windowOps = terminal.options.windowOptions ?? {};\n      windowOps.getWinSizePixels = true;\n      windowOps.getCellSizePixels = true;\n      windowOps.getWinSizeChars = true;\n      terminal.options.windowOptions = windowOps;\n    }\n\n    this._disposeLater(\n      this._renderer,\n      this._storage,\n\n      // DECSET/DECRST/DA1/XTSMGRAPHICS handlers\n      terminal.parser.registerCsiHandler({ prefix: '?', final: 'h' }, params => this._decset(params)),\n      terminal.parser.registerCsiHandler({ prefix: '?', final: 'l' }, params => this._decrst(params)),\n      terminal.parser.registerCsiHandler({ final: 'c' }, params => this._da1(params)),\n      terminal.parser.registerCsiHandler({ prefix: '?', final: 'S' }, params => this._xtermGraphicsAttributes(params)),\n\n      // render hook\n      terminal.onRender(range => this._storage?.render(range)),\n\n      /**\n       * reset handlers covered:\n       * - DECSTR\n       * - RIS\n       * - Terminal.reset()\n       */\n      terminal.parser.registerCsiHandler({ intermediates: '!', final: 'p' }, () => this.reset()),\n      terminal.parser.registerEscHandler({ final: 'c' }, () => this.reset()),\n      terminal._core._inputHandler.onRequestReset(() => this.reset()),\n\n      // wipe canvas and delete alternate images on buffer switch\n      terminal.buffer.onBufferChange(() => this._storage?.wipeAlternate()),\n\n      // extend images to the right on resize\n      terminal.onResize(metrics => this._storage?.viewportResize(metrics))\n    );\n\n    // SIXEL handler\n    if (this._opts.sixelSupport) {\n      const sixelStorage = new SixelImageStorage(this._storage!, this._opts, this._renderer!, terminal);\n      const sixelHandler = new SixelHandler(this._opts, sixelStorage, terminal);\n      this._handlers.set('sixel', sixelHandler);\n      this._disposeLater(\n        terminal._core._inputHandler._parser.registerDcsHandler({ final: 'q' }, sixelHandler)\n      );\n    }\n\n    // iTerm IIP handler\n    if (this._opts.iipSupport) {\n      const iipStorage = new IIPImageStorage(this._storage!);\n      const iipHandler = new IIPHandler(this._opts, this._renderer!, iipStorage, terminal);\n      this._handlers.set('iip', iipHandler);\n      this._disposeLater(\n        terminal._core._inputHandler._parser.registerOscHandler(1337, iipHandler)\n      );\n    }\n\n    // Kitty graphics handler\n    if (this._opts.kittySupport) {\n      const kittyStorage = new KittyImageStorage(this._storage!);\n      const kittyHandler = new KittyGraphicsHandler(this._opts, this._renderer!, kittyStorage, terminal);\n      this._handlers.set('kitty', kittyHandler);\n      this._disposeLater(\n        kittyStorage,\n        kittyHandler,\n        terminal._core._inputHandler._parser.registerApcHandler(0x47, kittyHandler)\n      );\n    }\n  }\n\n  // Note: storageLimit is skipped here to not intoduce a surprising side effect.\n  public reset(): boolean {\n    // reset options customizable by sequences to defaults\n    this._opts.sixelScrolling = this._defaultOpts.sixelScrolling;\n    this._opts.sixelPaletteLimit = this._defaultOpts.sixelPaletteLimit;\n    // also clear image storage\n    this._storage?.reset();\n    // reset protocol handlers\n    for (const handler of this._handlers.values()) {\n      handler.reset();\n    }\n    return false;\n  }\n\n  public get storageLimit(): number {\n    return this._storage?.getLimit() || -1;\n  }\n\n  public set storageLimit(limit: number) {\n    this._storage?.setLimit(limit);\n    this._opts.storageLimit = limit;\n  }\n\n  public get storageUsage(): number {\n    if (this._storage) {\n      return this._storage.getUsage();\n    }\n    return -1;\n  }\n\n  public get showPlaceholder(): boolean {\n    return this._opts.showPlaceholder;\n  }\n\n  public set showPlaceholder(value: boolean) {\n    this._opts.showPlaceholder = value;\n    this._renderer?.showPlaceholder(value);\n  }\n\n  public getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {\n    return this._storage?.getImageAtBufferCell(x, y);\n  }\n\n  public extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {\n    return this._storage?.extractTileAtBufferCell(x, y);\n  }\n\n  private _report(s: string): void {\n    this._terminal?._core.coreService.triggerDataEvent(s);\n  }\n\n  private _decset(params: (number | number[])[]): boolean {\n    for (let i = 0; i < params.length; ++i) {\n      switch (params[i]) {\n        case 80:\n          this._opts.sixelScrolling = false;\n          break;\n      }\n    }\n    return false;\n  }\n\n  private _decrst(params: (number | number[])[]): boolean {\n    for (let i = 0; i < params.length; ++i) {\n      switch (params[i]) {\n        case 80:\n          this._opts.sixelScrolling = true;\n          break;\n      }\n    }\n    return false;\n  }\n\n  // overload DA to return something more appropriate\n  private _da1(params: (number | number[])[]): boolean {\n    if (params[0]) {\n      return true;\n    }\n    // reported features:\n    // 62 - VT220\n    // 4 - SIXEL support\n    // 9 - charsets\n    // 22 - ANSI colors\n    if (this._opts.sixelSupport) {\n      this._report(`\\x1b[?62;4;9;22c`);\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Implementation of xterm's graphics attribute sequence.\n   *\n   * Supported features:\n   * - read/change palette limits (max 4096 by sixel lib)\n   * - read SIXEL canvas geometry (reports current window canvas or\n   *   squared pixelLimit if canvas > pixel limit)\n   *\n   * Everything else is deactivated.\n   */\n  private _xtermGraphicsAttributes(params: (number | number[])[]): boolean {\n    if (params.length < 2) {\n      return true;\n    }\n    if (params[0] === GaItem.COLORS) {\n      switch (params[1]) {\n        case GaAction.READ:\n          this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${this._opts.sixelPaletteLimit}S`);\n          return true;\n        case GaAction.SET_DEFAULT:\n          this._opts.sixelPaletteLimit = this._defaultOpts.sixelPaletteLimit;\n          this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${this._opts.sixelPaletteLimit}S`);\n          // also reset protocol handlers for now\n          for (const handler of this._handlers.values()) {\n            handler.reset();\n          }\n          return true;\n        case GaAction.SET:\n          if (params.length > 2 && !(params[2] instanceof Array) && params[2] <= MAX_SIXEL_PALETTE_SIZE) {\n            this._opts.sixelPaletteLimit = params[2];\n            this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${this._opts.sixelPaletteLimit}S`);\n          } else {\n            this._report(`\\x1b[?${params[0]};${GaStatus.ACTION_ERROR}S`);\n          }\n          return true;\n        case GaAction.READ_MAX:\n          this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${MAX_SIXEL_PALETTE_SIZE}S`);\n          return true;\n        default:\n          this._report(`\\x1b[?${params[0]};${GaStatus.ACTION_ERROR}S`);\n          return true;\n      }\n    }\n    if (params[0] === GaItem.SIXEL_GEO) {\n      switch (params[1]) {\n        // we only implement read and read_max here\n        case GaAction.READ:\n          let width = this._renderer?.dimensions?.css.canvas.width;\n          let height = this._renderer?.dimensions?.css.canvas.height;\n          if (!width || !height) {\n            // for some reason we have no working image renderer\n            // --> fallback to default cell size\n            const cellSize = CELL_SIZE_DEFAULT;\n            width = (this._terminal?.cols || 80) * cellSize.width;\n            height = (this._terminal?.rows || 24) * cellSize.height;\n          }\n          if (width * height < this._opts.pixelLimit) {\n            this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${width.toFixed(0)};${height.toFixed(0)}S`);\n          } else {\n            // if we overflow pixelLimit report that squared instead\n            const x = Math.floor(Math.sqrt(this._opts.pixelLimit));\n            this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${x};${x}S`);\n          }\n          return true;\n        case GaAction.READ_MAX:\n          // read_max returns pixelLimit as square area\n          const x = Math.floor(Math.sqrt(this._opts.pixelLimit));\n          this._report(`\\x1b[?${params[0]};${GaStatus.SUCCESS};${x};${x}S`);\n          return true;\n        default:\n          this._report(`\\x1b[?${params[0]};${GaStatus.ACTION_ERROR}S`);\n          return true;\n      }\n    }\n    // exit with error on ReGIS or any other requests\n    this._report(`\\x1b[?${params[0]};${GaStatus.ITEM_ERROR}S`);\n    return true;\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/ImageRenderer.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { toRGBA8888 } from 'sixel/lib/Colors';\nimport { IDisposable } from '@xterm/xterm';\nimport { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';\nimport { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\n\nconst PLACEHOLDER_LENGTH = 4096;\nconst PLACEHOLDER_HEIGHT = 24;\n\n/**\n * ImageRenderer - terminal frontend extension:\n * - provide primitives for canvas, ImageData, Bitmap (static)\n * - add canvas layer to DOM (browser only for now)\n * - draw image tiles onRender\n */\nexport class ImageRenderer extends Disposable implements IDisposable {\n  /** @deprecated Kept for backward compat — points to top layer canvas. */\n  public get canvas(): HTMLCanvasElement | undefined { return this._layers.get('top')?.canvas; }\n  private _layers = new Map<ImageLayer, CanvasRenderingContext2D>();\n  private _placeholder: HTMLCanvasElement | undefined;\n  private _placeholderBitmap: ImageBitmap | undefined;\n  private _optionsRefresh = this._register(new MutableDisposable());\n  private _oldOpen: ((parent: HTMLElement) => void) | undefined;\n  private _renderService: IRenderService | undefined;\n  private _oldSetRenderer: ((renderer: any) => void) | undefined;\n\n  // drawing primitive - canvas\n  public static createCanvas(localDocument: Document | undefined, width: number, height: number): HTMLCanvasElement {\n    /**\n     * NOTE: We normally dont care, from which document the canvas\n     * gets created, so we can fall back to global document,\n     * if the terminal has no document associated yet.\n     * This way early image loads before calling .open keep working\n     * (still discouraged though, as the metrics will be screwed up).\n     * Only the DOM output canvas should be on the terminal's document,\n     * which gets explicitly checked in `insertLayerToDom`.\n     */\n    const canvas = (localDocument ?? document).createElement('canvas');\n    canvas.width = width | 0;\n    canvas.height = height | 0;\n    return canvas;\n  }\n\n  // drawing primitive - ImageData with optional buffer\n  public static createImageData(ctx: CanvasRenderingContext2D, width: number, height: number, buffer?: ArrayBuffer): ImageData {\n    if (typeof ImageData !== 'function') {\n      const imgData = ctx.createImageData(width, height);\n      if (buffer) {\n        imgData.data.set(new Uint8ClampedArray(buffer, 0, width * height * 4));\n      }\n      return imgData;\n    }\n    return buffer\n      ? new ImageData(new Uint8ClampedArray(buffer, 0, width * height * 4), width, height)\n      : new ImageData(width, height);\n  }\n\n  // drawing primitive - ImageBitmap\n  public static createImageBitmap(img: ImageBitmapSource): Promise<ImageBitmap | undefined> {\n    if (typeof createImageBitmap !== 'function') {\n      return Promise.resolve(undefined);\n    }\n    return createImageBitmap(img);\n  }\n\n\n  constructor(private _terminal: ITerminalExt) {\n    super();\n    this._oldOpen = this._terminal._core.open;\n    this._terminal._core.open = (parent: HTMLElement): void => {\n      this._oldOpen?.call(this._terminal._core, parent);\n      this._open();\n    };\n    if (this._terminal._core.screenElement) {\n      this._open();\n    }\n    // hack to spot fontSize changes\n    this._optionsRefresh.value = this._terminal._core.optionsService.onOptionChange(option => {\n      if (option === 'fontSize') {\n        this.rescaleCanvas();\n        this._renderService?.refreshRows(0, this._terminal.rows);\n      }\n    });\n    this._register(toDisposable(() => {\n      this.removeLayerFromDom();\n      this.removeLayerFromDom('bottom');\n      if (this._terminal._core && this._oldOpen) {\n        this._terminal._core.open = this._oldOpen;\n        this._oldOpen = undefined;\n      }\n      if (this._renderService && this._oldSetRenderer) {\n        this._renderService.setRenderer = this._oldSetRenderer;\n        this._oldSetRenderer = undefined;\n      }\n      this._renderService = undefined;\n      this._layers.clear();\n      this._placeholderBitmap?.close();\n      this._placeholderBitmap = undefined;\n      this._placeholder = undefined;\n    }));\n  }\n\n  /**\n   * Enable the placeholder.\n   */\n  public showPlaceholder(value: boolean): void {\n    if (value) {\n      if (!this._placeholder && this.cellSize.height !== -1) {\n        this._createPlaceHolder(Math.max(this.cellSize.height + 1, PLACEHOLDER_HEIGHT));\n      }\n    } else {\n      this._placeholderBitmap?.close();\n      this._placeholderBitmap = undefined;\n      this._placeholder = undefined;\n    }\n    this._renderService?.refreshRows(0, this._terminal.rows);\n  }\n\n  /**\n   * Dimensions of the terminal.\n   * Forwarded from internal render service.\n   */\n  public get dimensions(): IRenderDimensions | undefined {\n    return this._terminal.dimensions;\n  }\n\n  /**\n   * Current cell size (float).\n   */\n  public get cellSize(): ICellSize {\n    return {\n      width: this.dimensions?.css.cell.width || -1,\n      height: this.dimensions?.css.cell.height || -1\n    };\n  }\n\n  /**\n   * Clear a region of the image layer canvas.\n   */\n  public clearLines(start: number, end: number, layer?: ImageLayer): void {\n    const y = start * (this.dimensions?.css.cell.height || 0);\n    const w = this.dimensions?.css.canvas.width || 0;\n    const h = (++end - start) * (this.dimensions?.css.cell.height || 0);\n    if (!layer || layer === 'top') {\n      this._layers.get('top')?.clearRect(0, y, w, h);\n    }\n    if (!layer || layer === 'bottom') {\n      this._layers.get('bottom')?.clearRect(0, y, w, h);\n    }\n  }\n\n  /**\n   * Clear whole image canvas.\n   */\n  public clearAll(layer?: ImageLayer): void {\n    if (!layer || layer === 'top') {\n      const ctx = this._layers.get('top');\n      ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n    }\n    if (!layer || layer === 'bottom') {\n      const ctx = this._layers.get('bottom');\n      ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n    }\n  }\n\n  /**\n   * Draw neighboring tiles on the image layer canvas.\n   */\n  public draw(imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number = 1): void {\n    const ctx = this._layers.get(imgSpec.layer);\n    if (!ctx) {\n      return;\n    }\n    const { width, height } = this.cellSize;\n\n    // Don't try to draw anything, if we cannot get valid renderer metrics.\n    if (width === -1 || height === -1) {\n      return;\n    }\n\n    this._rescaleImage(imgSpec, width, height);\n    const img = imgSpec.actual!;\n    const cols = Math.ceil(img.width / width);\n\n    const sx = (tileId % cols) * width;\n    const sy = Math.floor(tileId / cols) * height;\n    const dx = col * width;\n    const dy = row * height;\n\n    // safari bug: never access image source out of bounds\n    const finalWidth = count * width + sx > img.width ? img.width - sx : count * width;\n    const finalHeight = sy + height > img.height ? img.height - sy : height;\n\n    // Floor all pixel offsets to get stable tile mapping without any overflows.\n    // Note: For not pixel perfect aligned cells like in the DOM renderer\n    // this will move a tile slightly to the top/left (subpixel range, thus ignore it).\n    // FIX #34: avoid striping on displays with pixelDeviceRatio != 1 by ceiling height and width\n    ctx.drawImage(\n      img,\n      Math.floor(sx), Math.floor(sy), Math.ceil(finalWidth), Math.ceil(finalHeight),\n      Math.floor(dx), Math.floor(dy), Math.ceil(finalWidth), Math.ceil(finalHeight)\n    );\n  }\n\n  /**\n   * Extract a single tile from an image.\n   */\n  public extractTile(imgSpec: IImageSpec, tileId: number): HTMLCanvasElement | undefined {\n    const { width, height } = this.cellSize;\n    // Don't try to draw anything, if we cannot get valid renderer metrics.\n    if (width === -1 || height === -1) {\n      return;\n    }\n    this._rescaleImage(imgSpec, width, height);\n    const img = imgSpec.actual!;\n    const cols = Math.ceil(img.width / width);\n    const sx = (tileId % cols) * width;\n    const sy = Math.floor(tileId / cols) * height;\n    const finalWidth = width + sx > img.width ? img.width - sx : width;\n    const finalHeight = sy + height > img.height ? img.height - sy : height;\n\n    const canvas = ImageRenderer.createCanvas(this.document, finalWidth, finalHeight);\n    const ctx = canvas.getContext('2d');\n    if (ctx) {\n      ctx.drawImage(\n        img,\n        Math.floor(sx), Math.floor(sy), Math.floor(finalWidth), Math.floor(finalHeight),\n        0, 0, Math.floor(finalWidth), Math.floor(finalHeight)\n      );\n      return canvas;\n    }\n  }\n\n  /**\n   * Draw a line with placeholder on the image layer canvas.\n   */\n  public drawPlaceholder(col: number, row: number, count: number = 1): void {\n    const ctx = this._layers.get('top');\n    if (ctx) {\n      const { width, height } = this.cellSize;\n\n      // Don't try to draw anything, if we cannot get valid renderer metrics.\n      if (width === -1 || height === -1) {\n        return;\n      }\n\n      if (!this._placeholder) {\n        this._createPlaceHolder(Math.max(height + 1, PLACEHOLDER_HEIGHT));\n      } else if (height >= this._placeholder!.height) {\n        this._createPlaceHolder(height + 1);\n      }\n      if (!this._placeholder) return;\n      ctx.drawImage(\n        this._placeholderBitmap ?? this._placeholder!,\n        col * width,\n        (row * height) % 2 ? 0 : 1,  // needs %2 offset correction\n        width * count,\n        height,\n        col * width,\n        row * height,\n        width * count,\n        height\n      );\n    }\n  }\n\n  /**\n   * Rescale image layer canvas if needed.\n   * Checked once from `ImageStorage.render`.\n   */\n  public rescaleCanvas(): void {\n    const w = this.dimensions?.css.canvas.width || 0;\n    const h = this.dimensions?.css.canvas.height || 0;\n    for (const ctx of this._layers.values()) {\n      if (ctx.canvas.width !== w || ctx.canvas.height !== h) {\n        ctx.canvas.width = w;\n        ctx.canvas.height = h;\n      }\n    }\n  }\n\n  /**\n   * Rescale image in storage if needed.\n   */\n  private _rescaleImage(spec: IImageSpec, currentWidth: number, currentHeight: number): void {\n    if (currentWidth === spec.actualCellSize.width && currentHeight === spec.actualCellSize.height) {\n      return;\n    }\n    const { width: originalWidth, height: originalHeight } = spec.origCellSize;\n    if (currentWidth === originalWidth && currentHeight === originalHeight) {\n      spec.actual = spec.orig;\n      spec.actualCellSize.width = originalWidth;\n      spec.actualCellSize.height = originalHeight;\n      return;\n    }\n    const canvas = ImageRenderer.createCanvas(\n      this.document,\n      Math.ceil(spec.orig!.width * currentWidth / originalWidth),\n      Math.ceil(spec.orig!.height * currentHeight / originalHeight)\n    );\n    const ctx = canvas.getContext('2d');\n    if (ctx) {\n      ctx.drawImage(spec.orig!, 0, 0, canvas.width, canvas.height);\n      spec.actual = canvas;\n      spec.actualCellSize.width = currentWidth;\n      spec.actualCellSize.height = currentHeight;\n    }\n  }\n\n  /**\n   * Lazy init for the renderer.\n   */\n  private _open(): void {\n    this._renderService = this._terminal._core._renderService;\n    this._oldSetRenderer = this._renderService.setRenderer.bind(this._renderService);\n    this._renderService.setRenderer = (renderer: any) => {\n      for (const key of [...this._layers.keys()]) {\n        this.removeLayerFromDom(key);\n      }\n      this._oldSetRenderer?.call(this._renderService, renderer);\n    };\n  }\n\n  public insertLayerToDom(layer: ImageLayer = 'top'): void {\n    // make sure that the terminal is attached to a document and to DOM\n    if (!this.document || !this._terminal._core.screenElement) {\n      console.warn('image addon: cannot insert output canvas to DOM, missing document or screenElement');\n      return;\n    }\n    if (this._layers.has(layer)) {\n      return;\n    }\n    const canvas = ImageRenderer.createCanvas(\n      this.document, this.dimensions?.css.canvas.width || 0,\n      this.dimensions?.css.canvas.height || 0\n    );\n    canvas.classList.add(`xterm-image-layer-${layer}`);\n    const screenElement = this._terminal._core.screenElement;\n    // Use isolation to create a stacking context without overriding z-index,\n    // which would conflict with integrators (e.g. VS Code) that set their\n    // own z-index on the screen element.\n    screenElement.style.isolation = 'isolate';\n    if (layer === 'bottom') {\n      // Use z-index:-1 so it paints behind non-positioned text elements.\n      // The screen element needs to be a stacking context (via isolation)\n      // to contain the negative z-index, otherwise it would go behind the\n      // entire terminal.\n      canvas.style.zIndex = '-1';\n      screenElement.insertBefore(canvas, screenElement.firstChild);\n    } else {\n      // Explicit z-index ensures the image canvas reliably stacks above\n      // the text layer (DOM renderer rows). z-index: 0 is below the\n      // selection overlay (z-index: 1).\n      canvas.style.zIndex = '0';\n      screenElement.appendChild(canvas);\n    }\n    const ctx = canvas.getContext('2d', { alpha: true, desynchronized: true });\n    if (!ctx) {\n      canvas.remove();\n      return;\n    }\n    this._layers.set(layer, ctx);\n    this.clearAll(layer);\n  }\n\n  public removeLayerFromDom(layer: ImageLayer = 'top'): void {\n    const ctx = this._layers.get(layer);\n    if (ctx) {\n      ctx.canvas.remove();\n      this._layers.delete(layer);\n    }\n  }\n\n  public hasLayer(layer: ImageLayer): boolean {\n    return this._layers.has(layer);\n  }\n\n  private _createPlaceHolder(height: number = PLACEHOLDER_HEIGHT): void {\n    this._placeholderBitmap?.close();\n    this._placeholderBitmap = undefined;\n\n    // create blueprint to fill placeholder with\n    const bWidth = 32;  // must be 2^n\n    const blueprint = ImageRenderer.createCanvas(this.document, bWidth, height);\n    const ctx = blueprint.getContext('2d', { alpha: false });\n    if (!ctx) return;\n    const imgData = ImageRenderer.createImageData(ctx, bWidth, height);\n    const d32 = new Uint32Array(imgData.data.buffer);\n    const black = toRGBA8888(0, 0, 0);\n    const white = toRGBA8888(255, 255, 255);\n    d32.fill(black);\n    for (let y = 0; y < height; ++y) {\n      const shift = y % 2;\n      const offset = y * bWidth;\n      for (let x = 0; x < bWidth; x += 2) {\n        d32[offset + x + shift] = white;\n      }\n    }\n    ctx.putImageData(imgData, 0, 0);\n\n    // create placeholder line, width aligned to blueprint width\n    const width = (screen.width + bWidth - 1) & ~(bWidth - 1) || PLACEHOLDER_LENGTH;\n    this._placeholder = ImageRenderer.createCanvas(this.document, width, height);\n    const ctx2 = this._placeholder.getContext('2d', { alpha: false });\n    if (!ctx2) {\n      this._placeholder = undefined;\n      return;\n    }\n    for (let i = 0; i < width; i += bWidth) {\n      ctx2.drawImage(blueprint, i, 0);\n    }\n    ImageRenderer.createImageBitmap(this._placeholder).then(bitmap => this._placeholderBitmap = bitmap);\n  }\n\n  public get document(): Document | undefined {\n    return this._terminal._core._coreBrowserService?.window.document;\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/ImageStorage.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from '@xterm/xterm';\nimport { ImageRenderer } from './ImageRenderer';\nimport { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';\n\n\n// fallback default cell size\nexport const CELL_SIZE_DEFAULT: ICellSize = {\n  width: 7,\n  height: 14\n};\n\n/**\n * Extend extended attribute to also hold image tile information.\n *\n * Object definition is copied from base repo to fully mimick its behavior.\n * Image data is added as additional public properties `imageId` and `tileId`.\n */\nclass ExtendedAttrsImage implements IExtendedAttrsImage {\n  private _ext: number = 0;\n  public get ext(): number {\n    if (this._urlId) {\n      return (\n        (this._ext & ~ExtFlags.UNDERLINE_STYLE) |\n        (this.underlineStyle << 26)\n      );\n    }\n    return this._ext;\n  }\n  public set ext(value: number) { this._ext = value; }\n\n  public get underlineStyle(): UnderlineStyle {\n    // Always return the URL style if it has one\n    if (this._urlId) {\n      return UnderlineStyle.DASHED;\n    }\n    return (this._ext & ExtFlags.UNDERLINE_STYLE) >> 26;\n  }\n  public set underlineStyle(value: UnderlineStyle) {\n    this._ext &= ~ExtFlags.UNDERLINE_STYLE;\n    this._ext |= (value << 26) & ExtFlags.UNDERLINE_STYLE;\n  }\n\n  public get underlineColor(): number {\n    return this._ext & (Attributes.CM_MASK | Attributes.RGB_MASK);\n  }\n  public set underlineColor(value: number) {\n    this._ext &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n    this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK);\n  }\n\n  public get underlineVariantOffset(): number {\n    const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;\n    if (val < 0) {\n      return val ^ 0xFFFFFFF8;\n    }\n    return val;\n  }\n  public set underlineVariantOffset(value: number) {\n    this._ext &= ~ExtFlags.VARIANT_OFFSET;\n    this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;\n  }\n\n  private _urlId: number = 0;\n  public get urlId(): number {\n    return this._urlId;\n  }\n  public set urlId(value: number) {\n    this._urlId = value;\n  }\n\n  constructor(\n    ext: number = 0,\n    urlId: number = 0,\n    public imageId = -1,\n    public tileId = -1\n  ) {\n    this._ext = ext;\n    this._urlId = urlId;\n  }\n\n  public clone(): IExtendedAttrsImage {\n    /**\n     * Technically we dont need a clone variant of ExtendedAttrsImage,\n     * as we never clone a cell holding image data.\n     * Note: Clone is only meant to be used by the InputHandler for\n     * sticky attributes, which is never the case for image data.\n     * We still provide a proper clone method to reflect the full ext attr\n     * state in case there are future use cases for clone.\n     */\n    return new ExtendedAttrsImage(this._ext, this._urlId, this.imageId, this.tileId);\n  }\n\n  public isEmpty(): boolean {\n    return this.underlineStyle === UnderlineStyle.NONE && this._urlId === 0 && this.imageId === -1;\n  }\n}\nconst EMPTY_ATTRS = new ExtendedAttrsImage();\n\n\n/**\n * ImageStorage - extension of CoreTerminal:\n * - hold image data\n * - write/read image data to/from buffer\n *\n * TODO: image composition for overwrites\n */\nexport class ImageStorage implements IDisposable {\n  // storage\n  private _images: Map<number, IImageSpec> = new Map();\n  // last used id\n  private _lastId = 0;\n  // last evicted id\n  private _lowestId = 0;\n  // whether a full clear happened before\n  private _fullyCleared = false;\n  // whether render should do a full clear\n  private _needsFullClear = false;\n  // hard limit of stored pixels (fallback limit of 10 MB)\n  private _pixelLimit: number = 2500000;\n\n  private _viewportMetrics: { cols: number, rows: number };\n  public onImageAdded: (() => void) | undefined;\n  public onImageDeleted: ((storageId: number) => void) | undefined;\n\n  constructor(\n    private _terminal: ITerminalExt,\n    private _renderer: ImageRenderer,\n    private _opts: IImageAddonOptions\n  ) {\n    try {\n      this.setLimit(this._opts.storageLimit);\n    } catch (e: unknown) {\n      if (e instanceof Error) {\n        console.error(e.message);\n      }\n      console.warn(`storageLimit is set to ${this.getLimit()} MB`);\n    }\n    this._viewportMetrics = {\n      cols: this._terminal.cols,\n      rows: this._terminal.rows\n    };\n  }\n\n  public dispose(): void {\n    this.reset();\n  }\n\n  public reset(): void {\n    for (const spec of this._images.values()) {\n      spec.marker?.dispose();\n    }\n    // NOTE: marker.dispose above already calls ImageBitmap.close\n    // therefore we can just wipe the map here\n    this._images.clear();\n    this._renderer.clearAll();\n  }\n\n  public getLimit(): number {\n    return this._pixelLimit * 4 / 1000000;\n  }\n\n  public setLimit(value: number): void {\n    if (value < 0.5 || value > 1000) {\n      throw RangeError('invalid storageLimit, should be at least 0.5 MB and not exceed 1G');\n    }\n    this._pixelLimit = (value / 4 * 1000000) >>> 0;\n    this._evictOldest(0);\n  }\n\n  public getUsage(): number {\n    return this._getStoredPixels() * 4 / 1000000;\n  }\n\n  private _getStoredPixels(): number {\n    let storedPixels = 0;\n    for (const spec of this._images.values()) {\n      if (spec.orig) {\n        storedPixels += spec.orig.width * spec.orig.height;\n        if (spec.actual && spec.actual !== spec.orig) {\n          storedPixels += spec.actual.width * spec.actual.height;\n        }\n      }\n    }\n    return storedPixels;\n  }\n\n  private _delImg(id: number): void {\n    const spec = this._images.get(id);\n    if (!spec) return;\n    this._images.delete(id);\n    // FIXME: really ugly workaround to get bitmaps deallocated :(\n    if (window.ImageBitmap && spec.orig instanceof ImageBitmap) {\n      spec.orig.close();\n    }\n    this.onImageDeleted?.(id);\n  }\n\n  /**\n   * Wipe canvas and images on alternate buffer.\n   */\n  public wipeAlternate(): void {\n    // remove all alternate tagged images\n    const zero = [];\n    for (const [id, spec] of this._images.entries()) {\n      if (spec.bufferType === 'alternate') {\n        spec.marker?.dispose();\n        zero.push(id);\n      }\n    }\n    for (const id of zero) {\n      this._delImg(id);\n    }\n    // mark canvas to be wiped on next render\n    this._needsFullClear = true;\n    this._fullyCleared = false;\n  }\n\n  /**\n   * Delete an image by its internal storage ID.\n   * Used by protocols that support explicit deletion (e.g. Kitty a=d).\n   */\n  public deleteImage(id: number): void {\n    const spec = this._images.get(id);\n    if (spec) {\n      spec.marker?.dispose();\n      this._delImg(id);\n    }\n  }\n\n  /**\n   * Method to add an image to the storage.\n   * @param img - The image to add (canvas or bitmap).\n   * @param scrolling - When true, cursor advances with the image (lineFeed per row).\n   *   When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).\n   * @param layer - Which canvas layer to render on ('top' or 'bottom').\n   * @param zIndex - Z-index for image layering within the same layer.\n   * @returns The internal image ID assigned to the stored image.\n   */\n  public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer = 'top', zIndex: number = 0): number {\n    // never allow storage to exceed memory limit\n    this._evictOldest(img.width * img.height);\n\n    // calc rows x cols needed to display the image\n    let cellSize = this._renderer.cellSize;\n    if (cellSize.width === -1 || cellSize.height === -1) {\n      cellSize = CELL_SIZE_DEFAULT;\n    }\n    const cols = Math.ceil(img.width / cellSize.width);\n    const rows = Math.ceil(img.height / cellSize.height);\n\n    const imageId = ++this._lastId;\n\n    const buffer = this._terminal._core.buffer;\n    const termCols = this._terminal.cols;\n    const termRows = this._terminal.rows;\n    const originX = buffer.x;\n    const originY = buffer.y;\n    let offset = originX;\n    let tileCount = 0;\n\n    if (!scrolling) {\n      buffer.x = 0;\n      buffer.y = 0;\n      offset = 0;\n    }\n\n    this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);\n    for (let row = 0; row < rows; ++row) {\n      const line = buffer.lines.get(buffer.y + buffer.ybase);\n      for (let col = 0; col < cols; ++col) {\n        if (offset + col >= termCols) break;\n        this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);\n        tileCount++;\n      }\n      if (scrolling) {\n        if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();\n      } else {\n        if (++buffer.y >= termRows) break;\n      }\n      buffer.x = offset;\n    }\n    this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);\n\n    // cursor positioning modes\n    if (scrolling) {\n      buffer.x = offset;\n    } else {\n      buffer.x = originX;\n      buffer.y = originY;\n    }\n\n    // deleted images with zero tile count\n    const zero = [];\n    for (const [id, spec] of this._images.entries()) {\n      if (spec.tileCount < 1) {\n        spec.marker?.dispose();\n        zero.push(id);\n      }\n    }\n    for (const id of zero) {\n      this._delImg(id);\n    }\n\n    // eviction marker:\n    // delete the image when the marker gets disposed\n    const endMarker = this._terminal.registerMarker(0);\n    endMarker?.onDispose(() => {\n      const spec = this._images.get(imageId);\n      if (spec) {\n        this._delImg(imageId);\n      }\n    });\n\n    // since markers do not work on alternate for some reason,\n    // we evict images here manually\n    if (this._terminal.buffer.active.type === 'alternate') {\n      this._evictOnAlternate();\n    }\n\n    // create storage entry\n    const imgSpec: IImageSpec = {\n      orig: img,\n      origCellSize: cellSize,\n      actual: img,\n      actualCellSize: { ...cellSize },  // clone needed, since later modified\n      marker: endMarker || undefined,\n      tileCount,\n      bufferType: this._terminal.buffer.active.type,\n      layer,\n      zIndex\n    };\n\n    // finally add the image\n    this._images.set(imageId, imgSpec);\n    this.onImageAdded?.();\n    return imageId;\n  }\n\n\n  /**\n   * Render method. Collects buffer information and triggers\n   * canvas updates.\n   */\n  // TODO: Should we move this to the ImageRenderer?\n  public render(range: { start: number, end: number }): void {\n    // Determine which layers have images\n    let hasTopImages = false;\n    let hasBottomImages = false;\n    for (const spec of this._images.values()) {\n      if (spec.layer === 'bottom') {\n        hasBottomImages = true;\n      } else {\n        hasTopImages = true;\n      }\n      if (hasTopImages && hasBottomImages) break;\n    }\n\n    // Lazily insert layers that are needed\n    if (hasTopImages && !this._renderer.hasLayer('top')) {\n      this._renderer.insertLayerToDom('top');\n      if (!this._renderer.hasLayer('top')) return;\n    }\n    if (hasBottomImages && !this._renderer.hasLayer('bottom')) {\n      this._renderer.insertLayerToDom('bottom');\n    }\n\n    // rescale if needed\n    this._renderer.rescaleCanvas();\n\n    // exit early if we dont have any images to test for\n    if (!this._images.size) {\n      if (!this._fullyCleared) {\n        this._renderer.clearAll();\n        this._fullyCleared = true;\n        this._needsFullClear = false;\n      }\n      if (this._renderer.hasLayer('top')) {\n        this._renderer.removeLayerFromDom('top');\n      }\n      if (this._renderer.hasLayer('bottom')) {\n        this._renderer.removeLayerFromDom('bottom');\n      }\n      return;\n    }\n\n    // Remove layers no longer needed\n    if (!hasTopImages && this._renderer.hasLayer('top')) {\n      this._renderer.clearAll('top');\n      this._renderer.removeLayerFromDom('top');\n    }\n    if (!hasBottomImages && this._renderer.hasLayer('bottom')) {\n      this._renderer.clearAll('bottom');\n      this._renderer.removeLayerFromDom('bottom');\n    }\n\n    // buffer switches force a full clear\n    if (this._needsFullClear) {\n      this._renderer.clearAll();\n      this._fullyCleared = true;\n      this._needsFullClear = false;\n    }\n\n    const { start, end } = range;\n    const buffer = this._terminal._core.buffer;\n    const cols = this._terminal._core.cols;\n\n    // clear drawing area\n    this._renderer.clearLines(start, end);\n\n    // Collect draw calls so we can sort by z-index (lower z drawn first).\n    const drawCalls: { imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number }[] = [];\n    const placeholderCalls: { col: number, row: number, count: number }[] = [];\n\n    // walk all cells in viewport and collect tiles found\n    // Note: We check _extendedAttrs directly (not just HAS_EXTENDED flag)\n    // because text writes clear the BG flag but leave image tile data intact.\n    // This lets top-layer images survive text overwrites (kitty C=1 behavior).\n    for (let row = start; row <= end; ++row) {\n      const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;\n      if (!line) return;\n      for (let col = 0; col < cols; ++col) {\n        let e: IExtendedAttrsImage;\n        if (line.getBg(col) & BgFlags.HAS_EXTENDED) {\n          e = line._extendedAttrs[col] ?? EMPTY_ATTRS;\n        } else {\n          const maybeImg = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;\n          if (!maybeImg || maybeImg.imageId === undefined || maybeImg.imageId === -1) {\n            continue;\n          }\n          e = maybeImg;\n        }\n        const imageId = e.imageId;\n        if (imageId === undefined || imageId === -1) {\n          continue;\n        }\n        const imgSpec = this._images.get(imageId);\n        if (e.tileId !== -1) {\n          const startTile = e.tileId;\n          const startCol = col;\n          let count = 1;\n          /**\n           * merge tiles to the right into a single draw call, if:\n           * - not at end of line\n           * - cell has same image id\n           * - cell has consecutive tile id\n           * Also check _extendedAttrs directly for cells where text cleared HAS_EXTENDED.\n           */\n          while (++col < cols) {\n            const nextE = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;\n            if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {\n              break;\n            }\n            e = nextE;\n            count++;\n          }\n          col--;\n          if (imgSpec) {\n            if (imgSpec.actual) {\n              drawCalls.push({ imgSpec, tileId: startTile, col: startCol, row, count });\n            }\n          } else if (this._opts.showPlaceholder) {\n            placeholderCalls.push({ col: startCol, row, count });\n          }\n          this._fullyCleared = false;\n        }\n      }\n    }\n\n    // Sort by z-index so lower z draws first (higher z renders on top)\n    drawCalls.sort((a, b) => a.imgSpec.zIndex - b.imgSpec.zIndex);\n\n    // Draw placeholders first (lowest priority)\n    for (const call of placeholderCalls) {\n      this._renderer.drawPlaceholder(call.col, call.row, call.count);\n    }\n\n    // Draw images in z-index order\n    for (const call of drawCalls) {\n      this._renderer.draw(call.imgSpec, call.tileId, call.col, call.row, call.count);\n    }\n  }\n\n  public viewportResize(metrics: { cols: number, rows: number }): void {\n    // exit early if we have nothing in storage\n    if (!this._images.size) {\n      this._viewportMetrics = metrics;\n      return;\n    }\n\n    // handle only viewport width enlargements, exit all other cases\n    // TODO: needs patch for tile counter\n    if (this._viewportMetrics.cols >= metrics.cols) {\n      this._viewportMetrics = metrics;\n      return;\n    }\n\n    // walk scrollbuffer at old col width to find all possible expansion matches\n    const buffer = this._terminal._core.buffer;\n    const rows = buffer.lines.length;\n    const oldCol = this._viewportMetrics.cols - 1;\n    for (let row = 0; row < rows; ++row) {\n      const line = buffer.lines.get(row) as IBufferLineExt;\n      if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {\n        const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS;\n        const imageId = e.imageId;\n        if (imageId === undefined || imageId === -1) {\n          continue;\n        }\n        const imgSpec = this._images.get(imageId);\n        if (!imgSpec) {\n          continue;\n        }\n        // found an image tile at oldCol, check if it qualifies for right exapansion\n        const tilesPerRow = Math.ceil((imgSpec.actual?.width || 0) / imgSpec.actualCellSize.width);\n        if ((e.tileId % tilesPerRow) + 1 >= tilesPerRow) {\n          continue;\n        }\n        // expand only if right side is empty (nothing got wrapped from below)\n        let hasData = false;\n        for (let rightCol = oldCol + 1; rightCol > metrics.cols; ++rightCol) {\n          if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) {\n            hasData = true;\n            break;\n          }\n        }\n        if (hasData) {\n          continue;\n        }\n        // do right expansion on terminal buffer\n        const end = Math.min(metrics.cols, tilesPerRow - (e.tileId % tilesPerRow) + oldCol);\n        let lastTile = e.tileId;\n        for (let expandCol = oldCol + 1; expandCol < end; ++expandCol) {\n          this._writeToCell(line as IBufferLineExt, expandCol, imageId, ++lastTile);\n          imgSpec.tileCount++;\n        }\n      }\n    }\n    // store new viewport metrics\n    this._viewportMetrics = metrics;\n  }\n\n  /**\n   * Retrieve original canvas at buffer position.\n   */\n  public getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {\n    const buffer = this._terminal._core.buffer;\n    const line = buffer.lines.get(y) as IBufferLineExt;\n    if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {\n      const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS;\n      if (e.imageId && e.imageId !== -1) {\n        const orig = this._images.get(e.imageId)?.orig;\n        if (window.ImageBitmap && orig instanceof ImageBitmap) {\n          const canvas = ImageRenderer.createCanvas(window.document, orig.width, orig.height);\n          canvas.getContext('2d')?.drawImage(orig, 0, 0, orig.width, orig.height);\n          return canvas;\n        }\n        return orig as HTMLCanvasElement;\n      }\n    }\n  }\n\n  /**\n   * Extract active single tile at buffer position.\n   */\n  public extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {\n    const buffer = this._terminal._core.buffer;\n    const line = buffer.lines.get(y) as IBufferLineExt;\n    if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {\n      const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS;\n      if (e.imageId && e.imageId !== -1 && e.tileId !== -1) {\n        const spec = this._images.get(e.imageId);\n        if (spec) {\n          return this._renderer.extractTile(spec, e.tileId);\n        }\n      }\n    }\n  }\n\n  // TODO: Do we need some blob offloading tricks here to avoid early eviction?\n  // also see https://stackoverflow.com/questions/28307789/is-there-any-limitation-on-javascript-max-blob-size\n  private _evictOldest(room: number): number {\n    const used = this._getStoredPixels();\n    let current = used;\n    while (this._pixelLimit < current + room && this._images.size) {\n      const spec = this._images.get(++this._lowestId);\n      if (spec && spec.orig) {\n        current -= spec.orig.width * spec.orig.height;\n        if (spec.actual && spec.orig !== spec.actual) {\n          current -= spec.actual.width * spec.actual.height;\n        }\n        spec.marker?.dispose();\n        this._delImg(this._lowestId);\n      }\n    }\n    return used - current;\n  }\n\n  private _writeToCell(line: IBufferLineExt, x: number, imageId: number, tileId: number): void {\n    if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {\n      const old = line._extendedAttrs[x];\n      if (old) {\n        if (old.imageId !== undefined) {\n          // found an old ExtendedAttrsImage, since we know that\n          // they are always isolated instances (single cell usage),\n          // we can re-use it and just update their id entries\n          const oldSpec = this._images.get(old.imageId);\n          if (oldSpec) {\n            // early eviction for in-viewport overwrites\n            oldSpec.tileCount--;\n          }\n          old.imageId = imageId;\n          old.tileId = tileId;\n          return;\n        }\n        // found a plain ExtendedAttrs instance, clone it to new entry\n        line._extendedAttrs[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);\n        return;\n      }\n    }\n    // fall-through: always create new ExtendedAttrsImage entry\n    line._data[x * Cell.SIZE + Cell.BG] |= BgFlags.HAS_EXTENDED;\n    line._extendedAttrs[x] = new ExtendedAttrsImage(0, 0, imageId, tileId);\n  }\n\n  private _evictOnAlternate(): void {\n    // nullify tile count of all images on alternate buffer\n    for (const spec of this._images.values()) {\n      if (spec.bufferType === 'alternate') {\n        spec.tileCount = 0;\n      }\n    }\n    // re-count tiles on whole buffer\n    const buffer = this._terminal._core.buffer;\n    for (let y = 0; y < this._terminal.rows; ++y) {\n      const line = buffer.lines.get(y) as IBufferLineExt;\n      if (!line) {\n        continue;\n      }\n      for (let x = 0; x < this._terminal.cols; ++x) {\n        if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {\n          const imgId = line._extendedAttrs[x]?.imageId;\n          if (imgId) {\n            const spec = this._images.get(imgId);\n            if (spec) {\n              spec.tileCount++;\n            }\n          }\n        }\n      }\n    }\n    // deleted images with zero tile count\n    const zero = [];\n    for (const [id, spec] of this._images.entries()) {\n      if (spec.bufferType === 'alternate' && !spec.tileCount) {\n        spec.marker?.dispose();\n        zero.push(id);\n      }\n    }\n    for (const id of zero) {\n      this._delImg(id);\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/SixelHandler.ts",
    "content": "/**\n * Copyright (c) 2020, 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { SixelImageStorage } from './SixelImageStorage';\nimport { IDcsHandler, IParams, IImageAddonOptions, ITerminalExt, AttributeData, IResetHandler, ReadonlyColorSet } from './Types';\nimport { toRGBA8888, BIG_ENDIAN, PALETTE_ANSI_256, PALETTE_VT340_COLOR } from 'sixel/lib/Colors';\nimport { RGBA8888 } from 'sixel/lib/Types';\nimport { ImageRenderer } from './ImageRenderer';\n\nimport { DecoderAsync, Decoder } from 'sixel/lib/Decoder';\n\n// always free decoder ressources after decoding if it exceeds this limit\nconst MEM_PERMA_LIMIT = 4194304; // 1024 pixels * 1024 pixels * 4 channels = 4MB\n\n// custom default palette: VT340 (lower 16 colors) + ANSI256 (up to 256) + zeroed (up to 4096)\nconst DEFAULT_PALETTE = PALETTE_ANSI_256;\nDEFAULT_PALETTE.set(PALETTE_VT340_COLOR);\n\n\nexport class SixelHandler implements IDcsHandler, IResetHandler {\n  private _size = 0;\n  private _aborted = false;\n  private _dec: Decoder | undefined;\n\n  constructor(\n    private readonly _opts: IImageAddonOptions,\n    private readonly _storage: SixelImageStorage,\n    private readonly _coreTerminal: ITerminalExt\n  ) {\n    DecoderAsync({\n      memoryLimit: this._opts.pixelLimit * 4,\n      palette: DEFAULT_PALETTE,\n      paletteLimit: this._opts.sixelPaletteLimit\n    }).then(d => this._dec = d);\n  }\n\n  public reset(): void {\n    /**\n     * reset sixel decoder to defaults:\n     * - release all memory\n     * - nullify palette (4096)\n     * - apply default palette (256)\n     */\n    if (this._dec) {\n      this._dec.release();\n      // FIXME: missing interface on decoder to nullify full palette\n      (this._dec as any)._palette.fill(0);\n      this._dec.init(0, DEFAULT_PALETTE, this._opts.sixelPaletteLimit);\n    }\n  }\n\n  public hook(params: IParams): void {\n    this._size = 0;\n    this._aborted = false;\n    if (this._dec) {\n      const fillColor = params.params[1] === 1 ? 0 : extractActiveBg(\n        this._coreTerminal._core._inputHandler._curAttrData,\n        this._coreTerminal._core._themeService?.colors);\n      this._dec.init(fillColor, null, this._opts.sixelPaletteLimit);\n    }\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._aborted || !this._dec) {\n      return;\n    }\n    this._size += end - start;\n    if (this._size > this._opts.sixelSizeLimit) {\n      console.warn(`SIXEL: too much data, aborting`);\n      this._aborted = true;\n      this._dec.release();\n      return;\n    }\n    try {\n      this._dec.decode(data, start, end);\n    } catch (e) {\n      console.warn(`SIXEL: error while decoding image - ${e}`);\n      this._aborted = true;\n      this._dec.release();\n    }\n  }\n\n  public unhook(success: boolean): boolean | Promise<boolean> {\n    if (this._aborted || !success || !this._dec) {\n      return true;\n    }\n\n    const width = this._dec.width;\n    const height = this._dec.height;\n\n    // partial fix for https://github.com/jerch/xterm-addon-image/issues/37\n    if (!width || ! height) {\n      if (height) {\n        this._storage.advanceCursor(height);\n      }\n      return true;\n    }\n\n    const canvas = ImageRenderer.createCanvas(undefined, width, height);\n    canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8 as Uint8ClampedArray<ArrayBuffer>, width, height), 0, 0);\n    if (this._dec.memoryUsage > MEM_PERMA_LIMIT) {\n      this._dec.release();\n    }\n    this._storage.addImage(canvas);\n    return true;\n  }\n}\n\n\n/**\n * Some helpers to extract current terminal colors.\n */\n\n// get currently active background color from terminal\n// also respect INVERSE setting\nfunction extractActiveBg(attr: AttributeData, colors: ReadonlyColorSet | undefined): RGBA8888 {\n  let bg = 0;\n  if (!colors) {\n    // FIXME: theme service is prolly not available yet,\n    // happens if .open() was not called yet (bug in core?)\n    return bg;\n  }\n  if (attr.isInverse()) {\n    if (attr.isFgDefault()) {\n      bg = convertLe(colors.foreground.rgba);\n    } else if (attr.isFgRGB()) {\n      const t = (attr.constructor as typeof AttributeData).toColorRGB(attr.getFgColor());\n      bg = toRGBA8888(...t);\n    } else {\n      bg = convertLe(colors.ansi[attr.getFgColor()].rgba);\n    }\n  } else {\n    if (attr.isBgDefault()) {\n      bg = convertLe(colors.background.rgba);\n    } else if (attr.isBgRGB()) {\n      const t = (attr.constructor as typeof AttributeData).toColorRGB(attr.getBgColor());\n      bg = toRGBA8888(...t);\n    } else {\n      bg = convertLe(colors.ansi[attr.getBgColor()].rgba);\n    }\n  }\n  return bg;\n}\n\n// rgba values on the color managers are always in BE, thus convert to LE\nfunction convertLe(color: number): RGBA8888 {\n  if (BIG_ENDIAN) return color;\n  return (color & 0xFF) << 24 | (color >>> 8 & 0xFF) << 16 | (color >>> 16 & 0xFF) << 8 | color >>> 24 & 0xFF;\n}\n"
  },
  {
    "path": "addons/addon-image/src/SixelImageStorage.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';\nimport { IImageAddonOptions, ITerminalExt } from './Types';\nimport { ImageRenderer } from './ImageRenderer';\n\n/**\n * Sixel-specific image storage controller.\n *\n * Wraps the shared ImageStorage with sixel protocol semantics:\n * - Cursor behavior governed by DECSET 80 (sixelScrolling option)\n * - advanceCursor for empty sixels carrying only height\n */\nexport class SixelImageStorage {\n  constructor(\n    private readonly _storage: ImageStorage,\n    private readonly _opts: IImageAddonOptions,\n    private readonly _renderer: ImageRenderer,\n    private readonly _terminal: ITerminalExt\n  ) {}\n\n  /**\n   * Add a sixel image to storage.\n   * Cursor behavior depends on the sixelScrolling option (DECSET 80).\n   */\n  public addImage(img: HTMLCanvasElement | ImageBitmap): void {\n    this._storage.addImage(img, this._opts.sixelScrolling);\n  }\n\n  /**\n   * Only advance text cursor.\n   * This is an edge case from empty sixels carrying only a height but no pixels.\n   * Partially fixes https://github.com/jerch/xterm-addon-image/issues/37.\n   */\n  public advanceCursor(height: number): void {\n    if (this._opts.sixelScrolling) {\n      let cellSize = this._renderer.cellSize;\n      if (cellSize.width === -1 || cellSize.height === -1) {\n        cellSize = CELL_SIZE_DEFAULT;\n      }\n      const rows = Math.ceil(height / cellSize.height);\n      for (let i = 1; i < rows; ++i) {\n        this._terminal._core._inputHandler.lineFeed();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/Types.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable, IMarker, Terminal } from '@xterm/xterm';\n\n// private imports from base repo we build against\nimport { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';\nimport type { AttributeData } from 'common/buffer/AttributeData';\nimport type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';\nimport type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';\nimport type { ITerminal, ReadonlyColorSet } from 'browser/Types';\nimport type { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';\n\nexport const enum Cell {\n  CONTENT = 0,  // codepoint and wcwidth information (enum Content)\n  FG = 1,       // foreground color in lower 3 bytes (rgb), attrs in 4th byte (enum FgFlags)\n  BG = 2,       // background color in lower 3 bytes (rgb), attrs in 4th byte (enum BgFlags)\n  SIZE = 3      // size of single cell on buffer array\n}\n\n// export some privates for local usage\nexport { AttributeData, IParams, IDcsHandler, IOscHandler, IApcHandler, BgFlags, IRenderDimensions, IRenderService, Content, ExtFlags, Attributes, UnderlineStyle, ReadonlyColorSet };\n\n/**\n * Plugin ctor options.\n */\nexport interface IImageAddonOptions {\n  enableSizeReports: boolean;\n  pixelLimit: number;\n  storageLimit: number;\n  showPlaceholder: boolean;\n  sixelSupport: boolean;\n  sixelScrolling: boolean;\n  sixelPaletteLimit: number;\n  sixelSizeLimit: number;\n  iipSupport: boolean;\n  iipSizeLimit: number;\n  kittySupport: boolean;\n  kittySizeLimit: number;\n}\n\nexport interface IResetHandler {\n  // attached to RIS and DECSTR\n  reset(): void;\n}\n\n/**\n * Stub into private interfaces.\n * This should be kept in line with common libs.\n * Any change made here should be replayed in the accessors test case to\n * have a somewhat reliable testing against code changes in the core repo.\n */\n\n// overloaded IExtendedAttrs to hold image refs\nexport interface IExtendedAttrsImage extends IExtendedAttrs {\n  imageId: number;\n  tileId: number;\n  clone(): IExtendedAttrsImage;\n}\n\n/* eslint-disable */\nexport interface IBufferLineExt extends IBufferLine {\n  _extendedAttrs: {[index: number]: IExtendedAttrsImage | undefined};\n  _data: Uint32Array;\n}\n\ninterface IInputHandlerExt extends IInputHandler {\n  _parser: IEscapeSequenceParser;\n  _curAttrData: AttributeData;\n  _dirtyRowTracker: {\n    markRangeDirty(y1: number, y2: number): void;\n    markAllDirty(): void;\n    markDirty(y: number): void;\n  };\n  onRequestReset(handler: () => void): IDisposable;\n}\n\nexport interface ICoreTerminalExt extends ITerminal {\n  _themeService: IThemeService | undefined;\n  _inputHandler: IInputHandlerExt;\n  _renderService: IRenderService;\n  _coreBrowserService: ICoreBrowserService | undefined;\n}\n\nexport interface ITerminalExt extends Terminal {\n  _core: ICoreTerminalExt;\n}\n/* eslint-enable */\n\n\n/**\n * Some storage definitions.\n */\nexport interface ICellSize {\n  width: number;\n  height: number;\n}\n\nexport type ImageLayer = 'top' | 'bottom';\n\nexport interface IImageSpec {\n  orig: HTMLCanvasElement | ImageBitmap | undefined;\n  origCellSize: ICellSize;\n  actual: HTMLCanvasElement | ImageBitmap | undefined;\n  actualCellSize: ICellSize;\n  marker: IMarker | undefined;\n  tileCount: number;\n  bufferType: 'alternate' | 'normal';\n  layer: ImageLayer;\n  zIndex: number;\n}\n"
  },
  {
    "path": "addons/addon-image/src/kitty/KittyGraphicsHandler.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from '@xterm/xterm';\nimport { IApcHandler, IImageAddonOptions, IResetHandler, ITerminalExt, ImageLayer } from '../Types';\nimport { ImageRenderer } from '../ImageRenderer';\nimport { CELL_SIZE_DEFAULT } from '../ImageStorage';\nimport { KittyImageStorage } from './KittyImageStorage';\nimport Base64Decoder, { type DecodeStatus } from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';\nimport {\n  KittyAction,\n  KittyFormat,\n  KittyCompression,\n  IKittyCommand,\n  IPendingTransmission,\n  IKittyImageData,\n  BYTES_PER_PIXEL_RGB,\n  BYTES_PER_PIXEL_RGBA,\n  ALPHA_OPAQUE,\n  parseKittyCommand\n} from './KittyGraphicsTypes';\n\n// Memory limit for base64 decoder (4MB, same as IIPHandler)\nconst DECODER_KEEP_DATA = 4194304;\nconst DECODER_INITIAL_DATA = 4194304; // 4MB\n\n// Local mirror of const enum (esbuild can't inline const enums from external packages)\nconst DECODER_OK: DecodeStatus.OK = 0;\n\n// Maximum control data size\nconst MAX_CONTROL_DATA_SIZE = 512;\n\n// Semicolon codepoint\nconst SEMICOLON = 0x3B;\n\n// Kitty graphics protocol handler with streaming base64 decoding.\nexport class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDisposable {\n  private _aborted = false;\n  private _decodeError = false;\n\n  private _activeDecoder: Base64Decoder | null = null;\n  private readonly _maxEncodedBytes: number;\n  private readonly _initialEncodedBytes: number;\n\n  // Streaming related states\n\n  // True while receiving control data (before semicolon).\n  private _inControlData = true;\n\n  // Buffer for control data.\n  private _controlData = new Uint32Array(MAX_CONTROL_DATA_SIZE);\n  private _controlLength = 0;\n\n  // Pre-calculated encoded size limit\n  private _encodedSizeLimit = 0;\n  private _totalEncodedSize = 0;\n\n  // Parsed command. These are the control data before semicolon.\n  private _parsedCommand: IKittyCommand | null = null;\n\n  // Storage related states\n\n  private _pendingTransmissions: Map<number, IPendingTransmission> = new Map();\n  // Tracks the pending key of the most recently started chunked upload.\n  // Per spec, subsequent chunks only need m= (and optionally q=), without i=.\n  // When a chunk arrives with no i=, this key is used to find the pending upload.\n  private _lastPendingKey: number | undefined;\n\n  constructor(\n    private readonly _opts: IImageAddonOptions,\n    private readonly _renderer: ImageRenderer,\n    private readonly _kittyStorage: KittyImageStorage,\n    private readonly _coreTerminal: ITerminalExt\n  ) {\n    // Convert decoded size limit -> max encoded bytes.\n    this._maxEncodedBytes = Math.ceil(this._opts.kittySizeLimit * 4 / 3);\n    // ensure we preallocate more than configured limit while using 4mb initial size.\n    this._initialEncodedBytes = Math.min(DECODER_INITIAL_DATA, this._maxEncodedBytes);\n  }\n\n  public reset(): void {\n    this._cleanupAllPending();\n    if (this._activeDecoder) {\n      this._activeDecoder.release();\n      this._activeDecoder = null;\n    }\n    this._kittyStorage.reset();\n  }\n\n  public dispose(): void {\n    this.reset();\n  }\n\n  private _removePendingEntry(key: number): void {\n    this._pendingTransmissions.delete(key);\n    if (this._lastPendingKey === key) {\n      this._lastPendingKey = undefined;\n    }\n  }\n\n  private _cleanupAllPending(): void {\n    for (const pending of this._pendingTransmissions.values()) {\n      pending.decoder.release();\n    }\n    this._pendingTransmissions.clear();\n    this._lastPendingKey = undefined;\n  }\n\n  public start(): void {\n    this._aborted = false;\n    this._decodeError = false;\n    this._inControlData = true;\n    this._controlLength = 0;\n    this._parsedCommand = null;\n    // Pre-calculate encoded limit once: base64 is 4 bytes encoded → 3 bytes decoded\n    this._encodedSizeLimit = this._maxEncodedBytes;\n    this._totalEncodedSize = 0;\n    this._activeDecoder = null;\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._aborted) return;\n\n    if (!this._inControlData) {\n      this._streamPayload(data, start, end);\n    } else {\n      // Scan for semicolon\n      let controlEnd = end;\n      for (let i = start; i < end; i++) {\n        if (data[i] === SEMICOLON) {\n          this._inControlData = false;\n          controlEnd = i;\n          break;\n        }\n      }\n\n      // Copy control data\n      const copyLength = controlEnd - start;\n      if (this._controlLength + copyLength > MAX_CONTROL_DATA_SIZE) {\n        this._aborted = true;\n        return;\n      }\n      this._controlData.set(data.subarray(start, controlEnd), this._controlLength);\n      this._controlLength += copyLength;\n\n      if (!this._inControlData) {\n        // Found semicolon - parse control data early for validation\n        this._parsedCommand = parseKittyCommand(this._parseControlDataString());\n\n        // Early validation: i+I conflict\n        if (this._parsedCommand.id !== undefined && this._parsedCommand.imageNumber !== undefined) {\n          this._sendResponse(this._parsedCommand.id, 'EINVAL:cannot specify both i and I keys', this._parsedCommand.quiet ?? 0);\n          this._aborted = true;\n          return;\n        }\n\n        // Delete action doesn't need payload - skip streaming\n        if (this._parsedCommand.action === KittyAction.DELETE) {\n          return;\n        }\n\n        // Stream remaining as payload\n        const payloadStart = controlEnd + 1;\n        if (payloadStart < end) {\n          this._streamPayload(data, payloadStart, end);\n        }\n      }\n    }\n  }\n\n  // Stream payload bytes into the base64 decoder.\n  private _streamPayload(data: Uint32Array, start: number, end: number): void {\n    if (this._aborted) return;\n\n    // Check size limit (compare encoded bytes against pre-calculated limit)\n    // Include cumulative size from pending transmission for multi-chunk images.\n    // Per spec, subsequent chunks may omit i=, so fall back to _lastPendingKey.\n    const pendingKey = this._parsedCommand?.id ?? this._lastPendingKey ?? 0;\n    const pending = this._pendingTransmissions.get(pendingKey);\n    const previousEncodedSize = pending?.totalEncodedSize ?? 0;\n    this._totalEncodedSize += end - start;\n    const cumulativeEncodedSize = previousEncodedSize + this._totalEncodedSize;\n    if (cumulativeEncodedSize > this._encodedSizeLimit) {\n      const decoderToRelease = this._activeDecoder ?? pending?.decoder;\n      if (decoderToRelease) {\n        decoderToRelease.release();\n      }\n      this._activeDecoder = null;\n      if (pending) {\n        this._removePendingEntry(pendingKey);\n      }\n      this._aborted = true;\n      return;\n    }\n\n    if (this._decodeError) return;\n\n    if (pending?.decoder && !this._activeDecoder) {\n      this._activeDecoder = pending.decoder;\n    }\n    if (!this._activeDecoder) {\n      this._activeDecoder = new Base64Decoder(DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);\n      this._activeDecoder.init();\n    }\n\n    if (this._activeDecoder.put(data.subarray(start, end)) !== DECODER_OK) {\n      this._activeDecoder.release();\n      this._activeDecoder = null;\n      this._decodeError = true;\n      if (pending) {\n        this._removePendingEntry(pendingKey);\n      }\n    }\n  }\n\n  public end(success: boolean): boolean | Promise<boolean> {\n    if (this._aborted || !success) {\n      if (this._activeDecoder) {\n        this._activeDecoder.release();\n        this._activeDecoder = null;\n      }\n      return true;\n    }\n\n    // No semicolon = no payload (delete, capability query)\n    if (this._inControlData) {\n      return this._handleNoPayloadCommand();\n    }\n\n    // Use command parsed early in put() - i+I already validated there\n    const cmd = this._parsedCommand!;\n\n    // Delete action was handled by skipping payload - just execute\n    if (cmd.action === KittyAction.DELETE) {\n      return this._handleDelete(cmd);\n    }\n\n    // Per spec, subsequent chunks may omit i=, so fall back to _lastPendingKey.\n    const pendingKey = cmd.id ?? this._lastPendingKey ?? 0;\n    const isMoreComing = cmd.more === 1;\n    const pending = this._pendingTransmissions.get(pendingKey);\n\n    if (isMoreComing) {\n      if (this._activeDecoder) {\n        if (pending) {\n          pending.totalEncodedSize += this._totalEncodedSize;\n          pending.decodeError = pending.decodeError || this._decodeError;\n        } else {\n          this._pendingTransmissions.set(pendingKey, {\n            cmd: { ...cmd },\n            decoder: this._activeDecoder,\n            totalEncodedSize: this._totalEncodedSize,\n            decodeError: this._decodeError\n          });\n        }\n        this._lastPendingKey = pendingKey;\n        this._activeDecoder = null;\n      }\n      return true;\n    }\n\n    // Final chunk received — clear the last pending key\n    if (pending) {\n      this._lastPendingKey = undefined;\n    }\n\n    let decodeError = this._decodeError;\n    let finalCmd = cmd;\n    let decoder = this._activeDecoder;\n\n    if (pending) {\n      finalCmd = pending.cmd;\n      decoder = pending.decoder;\n      decodeError = decodeError || pending.decodeError;\n      this._pendingTransmissions.delete(pendingKey);\n    }\n\n    let imageBytes = new Uint8Array(0);\n    if (decoder) {\n      if (decoder.end() !== DECODER_OK) {\n        decodeError = true;\n      }\n      imageBytes = decoder.data8;\n    }\n    this._activeDecoder = null;\n\n    // Handle command first — handlers create Blob/ImageData from imageBytes,\n    // which copies the data. Only then is it safe to release the decoder's\n    // wasm memory that imageBytes points into.\n    const result = this._handleCommandWithBytesAndCmd(finalCmd, imageBytes, decodeError);\n    if (decoder) {\n      decoder.release();\n    }\n    return result;\n  }\n\n  // Command handling\n\n  private _parseControlDataString(): string {\n    let str = '';\n    for (let i = 0; i < this._controlLength; i++) {\n      str += String.fromCodePoint(this._controlData[i]);\n    }\n    return str;\n  }\n\n  private _handleNoPayloadCommand(): boolean | Promise<boolean> {\n    const cmd = parseKittyCommand(this._parseControlDataString());\n\n    // Per spec: specifying both i and I is an error\n    if (cmd.id !== undefined && cmd.imageNumber !== undefined) {\n      this._sendResponse(cmd.id, 'EINVAL:cannot specify both i and I keys', cmd.quiet ?? 0);\n      return true;\n    }\n\n    const action = cmd.action ?? 't';\n\n    switch (action) {\n      case KittyAction.DELETE:\n        return this._handleDelete(cmd);\n      case KittyAction.QUERY:\n        this._sendResponse(cmd.id ?? 0, 'OK', cmd.quiet ?? 0);\n        return true;\n      case KittyAction.PLACEMENT:\n        return this._handlePlacement(cmd);\n      default:\n        // TODO: Implement remaining actions when needed:\n        // - a=f (frame): animation frame operations\n        // - a=a (animation): animation control\n        // - a=c (compose): compose images\n        if (cmd.id !== undefined) {\n          this._sendResponse(cmd.id, 'EINVAL:unsupported action', cmd.quiet ?? 0);\n        }\n        return true;\n    }\n  }\n\n  private _handleCommandWithBytesAndCmd(cmd: IKittyCommand, bytes: Uint8Array, decodeError: boolean): boolean | Promise<boolean> {\n    const action = cmd.action ?? 't';\n\n    switch (action) {\n      case KittyAction.TRANSMIT: {\n        const result = this._handleTransmit(cmd, bytes, decodeError);\n        // Only send response when _handleTransmit didn't already respond\n        // (it handles unsupported transmission medium responses internally)\n        if ((cmd.transmission ?? 'd') === 'd' && cmd.id !== undefined) {\n          if (decodeError) {\n            this._sendResponse(cmd.id, 'EINVAL:invalid base64 data', cmd.quiet ?? 0);\n          } else if (bytes.length > 0) {\n            this._sendResponse(cmd.id, 'OK', cmd.quiet ?? 0);\n          }\n        }\n        return result;\n      }\n      case KittyAction.TRANSMIT_DISPLAY:\n        return this._handleTransmitDisplay(cmd, bytes, decodeError);\n      case KittyAction.QUERY:\n        return this._handleQuery(cmd, bytes, decodeError);\n      case KittyAction.PLACEMENT:\n        // a=p ignores any payload — image data was already transmitted\n        return this._handlePlacement(cmd);\n      default:\n        // TODO: Implement remaining actions when needed:\n        // - a=f (frame): animation frame operations\n        // - a=a (animation): animation control\n        // - a=c (compose): compose images\n        if (cmd.id !== undefined) {\n          this._sendResponse(cmd.id, 'EINVAL:unsupported action', cmd.quiet ?? 0);\n        }\n        return true;\n    }\n  }\n\n  private _handlePlacement(cmd: IKittyCommand): boolean | Promise<boolean> {\n    if (cmd.id === undefined) {\n      return true;\n    }\n    const id = cmd.id;\n    const image = this._kittyStorage.getImage(id);\n    if (!image) {\n      this._sendResponse(id, 'ENOENT:image not found', cmd.quiet ?? 0, cmd.placementId);\n      return true;\n    }\n    const result = this._displayImage(image, cmd);\n    return result.then(success => {\n      this._sendResponse(id, success ? 'OK' : 'EINVAL:image rendering failed', cmd.quiet ?? 0, cmd.placementId);\n      return true;\n    });\n  }\n\n  private _handleTransmit(cmd: IKittyCommand, bytes: Uint8Array, decodeError: boolean): boolean {\n    // TODO: Support file-based transmission modes (t=f, t=t, t=s)\n    // Currently only supports direct transmission (t=d, the default).\n    // - t=f (file): Payload is base64-encoded file path. Terminal reads image from that path.\n    // - t=t (temp file): Payload is base64-encoded path in temp directory. Terminal reads, deletes.\n    // - t=s: Payload is base64-encoded POSIX shm name. Terminal reads from shared memory.\n    // These modes require filesystem/IPC access not available in browsers. For Node.js/Electron:\n    // 1. Check cmd.transmission (t key) before treating bytes as image data\n    // 2. For t=f/t/s: decode bytes as UTF-8 string (the path/name), then read file contents\n    // 3. For t=d: treat bytes as image data (current behavior)\n    // When implementing, also update _handleQuery to accept these transmission mediums.\n    const transmission = cmd.transmission ?? 'd';\n    if (transmission !== 'd') {\n      if (cmd.id !== undefined) {\n        this._sendResponse(cmd.id, 'EINVAL:unsupported transmission medium', cmd.quiet ?? 0);\n      }\n      return true;\n    }\n\n    if (decodeError || bytes.length === 0) return true;\n\n    this._kittyStorage.storeImage(cmd.id, {\n      data: new Blob([bytes as BlobPart]),\n      width: cmd.width ?? 0,\n      height: cmd.height ?? 0,\n      format: (cmd.format ?? KittyFormat.RGBA) as 24 | 32 | 100,\n      compression: cmd.compression ?? ''\n    });\n    return true;\n  }\n\n  private _handleTransmitDisplay(cmd: IKittyCommand, bytes: Uint8Array, decodeError: boolean): boolean | Promise<boolean> {\n    if (decodeError) {\n      if (cmd.id !== undefined) {\n        this._sendResponse(cmd.id, 'EINVAL:invalid base64 data', cmd.quiet ?? 0);\n      }\n      return true;\n    }\n\n    this._handleTransmit(cmd, bytes, decodeError);\n\n    const id = cmd.id ?? this._kittyStorage.lastImageId;\n    const image = this._kittyStorage.getImage(id);\n    if (image) {\n      const result = this._displayImage(image, cmd);\n      if (cmd.id !== undefined) {\n        return result.then(success => {\n          this._sendResponse(id, success ? 'OK' : 'EINVAL:image rendering failed', cmd.quiet ?? 0);\n          return true;\n        });\n      }\n      return result.then(() => true);\n    }\n    return true;\n  }\n\n  private _handleQuery(cmd: IKittyCommand, bytes: Uint8Array, decodeError: boolean): boolean {\n    const id = cmd.id ?? 0;\n    const quiet = cmd.quiet ?? 0;\n\n    // Per spec: reject unsupported transmission mediums (only t=d is supported atm)\n    // TODO: When filesystem support is added (Node.js/Electron), update this to accept\n    // t=f (file), t=t (temp file), and t=s (shared memory) and respond OK for queries.\n    const transmission = cmd.transmission ?? 'd';\n    if (transmission !== 'd') {\n      this._sendResponse(id, 'EINVAL:unsupported transmission medium', quiet);\n      return true;\n    }\n\n    // Check decode error first (invalid base64)\n    if (decodeError) {\n      this._sendResponse(id, 'EINVAL:invalid base64 data', quiet);\n      return true;\n    }\n\n    // Capability query (no payload) - just respond OK\n    if (bytes.length === 0) {\n      this._sendResponse(id, 'OK', quiet);\n      return true;\n    }\n\n    const format = cmd.format ?? KittyFormat.RGBA;\n\n    if (format === KittyFormat.PNG) {\n      this._sendResponse(id, 'OK', quiet);\n    } else {\n      const width = cmd.width ?? 0;\n      const height = cmd.height ?? 0;\n\n      if (!width || !height) {\n        this._sendResponse(id, 'EINVAL:width and height required for raw pixel data', quiet);\n        return true;\n      }\n\n      const bytesPerPixel = format === KittyFormat.RGBA ? BYTES_PER_PIXEL_RGBA : BYTES_PER_PIXEL_RGB;\n      const expectedBytes = width * height * bytesPerPixel;\n\n      if (bytes.length < expectedBytes) {\n        this._sendResponse(id, `EINVAL:insufficient pixel data`, quiet);\n        return true;\n      }\n\n      this._sendResponse(id, 'OK', quiet);\n    }\n    return true;\n  }\n\n  private _handleDelete(cmd: IKittyCommand): boolean {\n    // Per spec: default delete selector is 'a' (delete all visible placements)\n    const selector = cmd.deleteSelector ?? 'a';\n\n    // TODO: Distinguish lowercase (delete placements only) from uppercase\n    // (delete placements + free stored image data). Currently both variants\n    // free everything since we don't separate stored data from placements.\n    switch (selector) {\n      case 'a':\n      case 'A':\n        this._cleanupAllPending();\n        this._kittyStorage.deleteAll();\n        break;\n      case 'i':\n      case 'I':\n        // TODO: When placement id tracking is implemented (see TODO in\n        // KittyImageStorage), d=i with p=<pid> should delete only that\n        // specific placement, while d=i without p should delete all\n        // placements for the image.\n        if (cmd.id !== undefined) {\n          const pending = this._pendingTransmissions.get(cmd.id);\n          if (pending) {\n            pending.decoder.release();\n          }\n          this._removePendingEntry(cmd.id);\n          this._kittyStorage.deleteById(cmd.id);\n        }\n        break;\n      default:\n        // Unsupported selectors (c, n, p, q, r, x, y, z, f) — ignore for now\n        break;\n    }\n    return true;\n  }\n\n  private _sendResponse(id: number, message: string, quiet: number, placementId?: number): void {\n    const isOk = message === 'OK';\n    if (isOk && quiet === 1) return;\n    if (!isOk && quiet === 2) return;\n\n    const pPart = placementId ? `,p=${placementId}` : '';\n    const response = `\\x1b_Gi=${id}${pPart};${message}\\x1b\\\\`;\n    this._coreTerminal._core.coreService.triggerDataEvent(response);\n  }\n\n  // Image display\n\n  private _displayImage(image: IKittyImageData, cmd: IKittyCommand): Promise<boolean> {\n    return this._decodeAndDisplay(image, cmd)\n      .then(() => true)\n      .catch(() => false);\n  }\n\n  private async _decodeAndDisplay(image: IKittyImageData, cmd: IKittyCommand): Promise<void> {\n    let bitmap: ImageBitmap | undefined = await this._createBitmap(image);\n\n    try {\n      const cropX = Math.max(0, cmd.x ?? 0);\n      const cropY = Math.max(0, cmd.y ?? 0);\n      const cropW = cmd.sourceWidth || (bitmap.width - cropX);\n      const cropH = cmd.sourceHeight || (bitmap.height - cropY);\n\n      const maxCropW = Math.max(0, bitmap.width - cropX);\n      const maxCropH = Math.max(0, bitmap.height - cropY);\n      const finalCropW = Math.max(0, Math.min(cropW, maxCropW));\n      const finalCropH = Math.max(0, Math.min(cropH, maxCropH));\n\n      if (finalCropW === 0 || finalCropH === 0) {\n        throw new Error('invalid source rectangle');\n      }\n\n      if (cropX !== 0 || cropY !== 0 || finalCropW !== bitmap.width || finalCropH !== bitmap.height) {\n        const cropped = await createImageBitmap(bitmap, cropX, cropY, finalCropW, finalCropH);\n        bitmap.close();\n        bitmap = cropped;\n      }\n\n      const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;\n      const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;\n\n      // Per spec: c/r default to image's natural cell dimensions.\n      // If only one of c/r is specified, compute the other from image aspect ratio.\n      let imgCols: number;\n      let imgRows: number;\n      if (cmd.columns !== undefined && cmd.rows !== undefined) {\n        imgCols = cmd.columns;\n        imgRows = cmd.rows;\n      } else if (cmd.columns !== undefined) {\n        imgCols = cmd.columns;\n        imgRows = Math.max(1, Math.ceil((bitmap.height / bitmap.width) * (imgCols * cw) / ch));\n      } else if (cmd.rows !== undefined) {\n        imgRows = cmd.rows;\n        imgCols = Math.max(1, Math.ceil((bitmap.width / bitmap.height) * (imgRows * ch) / cw));\n      } else {\n        imgCols = Math.ceil(bitmap.width / cw);\n        imgRows = Math.ceil(bitmap.height / ch);\n      }\n\n      let w = bitmap.width;\n      let h = bitmap.height;\n\n      // Scale bitmap to fit placement rectangle when c/r are specified\n      if (cmd.columns !== undefined || cmd.rows !== undefined) {\n        w = Math.round(imgCols * cw);\n        h = Math.round(imgRows * ch);\n      }\n\n      if (w * h > this._opts.pixelLimit) {\n        throw new Error('image exceeds pixel limit');\n      }\n\n      // Save cursor position before addImage modifies it\n      const buffer = this._coreTerminal._core.buffer;\n      const savedX = buffer.x;\n      const savedY = buffer.y;\n      const savedYbase = buffer.ybase;\n\n      // Determine layer based on z-index: negative = behind text, 0+ = on top.\n      // When z<0 we always use the bottom layer even without allowTransparency —\n      // the image will simply be hidden behind the opaque text background, which\n      // is the correct behavior (client asked for \"behind text\").\n      const wantsBottom = cmd.zIndex !== undefined && cmd.zIndex < 0;\n      const layer: ImageLayer = wantsBottom ? 'bottom' : 'top';\n\n      if (w !== bitmap.width || h !== bitmap.height) {\n        const scaled = await createImageBitmap(bitmap, { resizeWidth: w, resizeHeight: h });\n        bitmap.close();\n        bitmap = scaled;\n      }\n\n      // Per spec: X/Y are pixel offsets within the first cell, so clamp to cell dimensions\n      const xOffset = Math.min(Math.max(0, cmd.xOffset ?? 0), cw - 1);\n      const yOffset = Math.min(Math.max(0, cmd.yOffset ?? 0), ch - 1);\n      if (xOffset !== 0 || yOffset !== 0) {\n        // Per spec: X/Y is not added to c/r area. When c/r are explicit, the\n        // total placement area remains c*cw × r*ch pixels and the offset image\n        // is clipped to fit. When c/r are unset, the padded canvas determines\n        // the natural cell dimensions.\n        const canvasW = (cmd.columns !== undefined) ? Math.round(imgCols * cw) : bitmap.width + xOffset;\n        const canvasH = (cmd.rows !== undefined) ? Math.round(imgRows * ch) : bitmap.height + yOffset;\n        const offsetCanvas = ImageRenderer.createCanvas(window.document, canvasW, canvasH);\n        const offsetCtx = offsetCanvas.getContext('2d');\n        if (!offsetCtx) {\n          throw new Error('Failed to create offset canvas context');\n        }\n        offsetCtx.drawImage(bitmap, xOffset, yOffset);\n\n        const offsetBitmap = await createImageBitmap(offsetCanvas);\n        offsetCanvas.width = offsetCanvas.height = 0;\n        bitmap.close();\n        bitmap = offsetBitmap;\n        w = bitmap.width;\n        h = bitmap.height;\n        if (w * h > this._opts.pixelLimit) {\n          throw new Error('image exceeds pixel limit');\n        }\n        if (cmd.columns === undefined) {\n          imgCols = Math.ceil(bitmap.width / cw);\n        }\n        if (cmd.rows === undefined) {\n          imgRows = Math.ceil(bitmap.height / ch);\n        }\n      }\n\n      const zIndex = cmd.zIndex ?? 0;\n      this._kittyStorage.addImage(image.id, bitmap, true, layer, zIndex);\n      bitmap = undefined;  // ownership transferred to storage\n\n      // Kitty cursor movement\n      // Per spec: cursor placed at first column after last image column,\n      // on the last row of the image. C=1 means don't move cursor.\n      if (cmd.cursorMovement === 1) {\n        // C=1: restore cursor to position before image was placed\n        const scrolled = buffer.ybase - savedYbase;\n        buffer.x = savedX;\n        // Can't restore cursor to scrollback?\n        buffer.y = Math.max(savedY - scrolled, 0);\n      } else {\n        // Default (C=0): advance cursor horizontally past the image\n        // addImage already positioned cursor on the last row via lineFeeds\n        buffer.x = Math.min(savedX + imgCols, this._coreTerminal.cols);\n      }\n    } catch (e) {\n      bitmap?.close();\n      throw e;\n    }\n  }\n\n  // Create ImageBitmap from already-decoded image data.\n  private async _createBitmap(image: IKittyImageData): Promise<ImageBitmap> {\n    let bytes: Uint8Array = new Uint8Array(await image.data.arrayBuffer());\n\n    if (image.compression === KittyCompression.ZLIB) {\n      bytes = await this._decompressZlib(bytes);\n    }\n\n    if (image.format === KittyFormat.PNG) {\n      const blob = new Blob([bytes as BlobPart], { type: 'image/png' });\n      if (!window.createImageBitmap) {\n        const url = URL.createObjectURL(blob);\n        const img = new Image();\n        return new Promise<ImageBitmap>((resolve, reject) => {\n          img.addEventListener('load', () => {\n            URL.revokeObjectURL(url);\n            const canvas = ImageRenderer.createCanvas(window.document, img.width, img.height);\n            canvas.getContext('2d')?.drawImage(img, 0, 0);\n            createImageBitmap(canvas).then(resolve).catch(reject);\n          });\n          img.addEventListener('error', () => {\n            URL.revokeObjectURL(url);\n            reject(new Error('Failed to load image'));\n          });\n          img.src = url;\n        });\n      }\n      return createImageBitmap(blob);\n    }\n\n    // Raw pixel data\n    const width = image.width;\n    const height = image.height;\n\n    if (!width || !height) {\n      throw new Error('Width and height required for raw pixel data');\n    }\n\n    const bytesPerPixel = image.format === KittyFormat.RGBA ? BYTES_PER_PIXEL_RGBA : BYTES_PER_PIXEL_RGB;\n    const expectedBytes = width * height * bytesPerPixel;\n\n    if (bytes.length < expectedBytes) {\n      throw new Error('Insufficient pixel data');\n    }\n\n    const pixelCount = width * height;\n\n    if (image.format === KittyFormat.RGBA) {\n      // RGBA: use bytes directly — no copy needed\n      return createImageBitmap(new ImageData(new Uint8ClampedArray(bytes.buffer as ArrayBuffer, bytes.byteOffset, pixelCount * BYTES_PER_PIXEL_RGBA), width, height));\n    }\n\n    // RGB→RGBA: interleave alpha using uint32 block processing (4 pixels per iteration).\n    // 3 uint32 reads + 4 uint32 writes per 4 pixels vs 28 byte reads/writes — ~6x faster.\n    // Assumes little-endian (all modern browsers/Node.js).\n    const data = new Uint8ClampedArray(pixelCount * BYTES_PER_PIXEL_RGBA);\n    const src32 = new Uint32Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 4));\n    const dst32 = new Uint32Array(data.buffer);\n    const alignedPixels = pixelCount & ~3;  // round down to multiple of 4\n\n    let srcOffset = 0;\n    let dstOffset = 0;\n    for (let i = 0; i < alignedPixels; i += 4) {\n      const b0 = src32[srcOffset++];\n      const b1 = src32[srcOffset++];\n      const b2 = src32[srcOffset++];\n      // Little-endian: pixel bytes are [R,G,B] → uint32 ABGR layout\n      dst32[dstOffset++] = 0xFF000000 | b0;\n      dst32[dstOffset++] = 0xFF000000 | (b0 >>> 24) | (b1 << 8);\n      dst32[dstOffset++] = 0xFF000000 | (b1 >>> 16) | (b2 << 16);\n      dst32[dstOffset++] = 0xFF000000 | (b2 >>> 8);\n    }\n\n    // Handle remaining 1–3 pixels\n    let srcByte = alignedPixels * BYTES_PER_PIXEL_RGB;\n    let dstByte = alignedPixels * BYTES_PER_PIXEL_RGBA;\n    for (let i = alignedPixels; i < pixelCount; i++) {\n      data[dstByte]     = bytes[srcByte];\n      data[dstByte + 1] = bytes[srcByte + 1];\n      data[dstByte + 2] = bytes[srcByte + 2];\n      data[dstByte + 3] = ALPHA_OPAQUE;\n      srcByte += BYTES_PER_PIXEL_RGB;\n      dstByte += BYTES_PER_PIXEL_RGBA;\n    }\n\n    return createImageBitmap(new ImageData(data, width, height));\n  }\n\n  private async _decompressZlib(compressed: Uint8Array): Promise<Uint8Array> {\n    try {\n      return await this._decompress(compressed, 'deflate');\n    } catch {\n      return await this._decompress(compressed, 'deflate-raw');\n    }\n  }\n\n  private async _decompress(compressed: Uint8Array, format: 'deflate' | 'deflate-raw'): Promise<Uint8Array> {\n    const ds = new DecompressionStream(format);\n    const writer = ds.writable.getWriter();\n    writer.write(compressed as BufferSource);\n    writer.close();\n\n    const chunks: Uint8Array[] = [];\n    const reader = ds.readable.getReader();\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      chunks.push(value);\n    }\n\n    const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n    const result = new Uint8Array(totalLength);\n    let offset = 0;\n    for (const chunk of chunks) {\n      result.set(chunk, offset);\n      offset += chunk.length;\n    }\n    return result;\n  }\n\n  public get images(): ReadonlyMap<number, IKittyImageData> {\n    return this._kittyStorage.images;\n  }\n\n  public get _kittyIdToStorageId(): ReadonlyMap<number, number> {\n    return this._kittyStorage.kittyIdToStorageId;\n  }\n\n  public get pendingTransmissions(): ReadonlyMap<number, IPendingTransmission> {\n    return this._pendingTransmissions;\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/kitty/KittyGraphicsTypes.test.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { parseKittyCommand, KittyAction, KittyFormat } from './KittyGraphicsTypes';\n\ndescribe('KittyGraphicsTypes', () => {\n  describe('parseKittyCommand', () => {\n    it('should parse control data with action and format', () => {\n      const cmd = parseKittyCommand('a=T,f=100');\n      assert.strictEqual(cmd.action, 'T');\n      assert.strictEqual(cmd.format, 100);\n    });\n\n    it('should parse control data with all options', () => {\n      const cmd = parseKittyCommand('a=t,f=32,i=5,s=10,v=20,c=3,r=2,m=1,q=2');\n      assert.strictEqual(cmd.action, 't');\n      assert.strictEqual(cmd.format, 32);\n      assert.strictEqual(cmd.id, 5);\n      assert.strictEqual(cmd.width, 10);\n      assert.strictEqual(cmd.height, 20);\n      assert.strictEqual(cmd.columns, 3);\n      assert.strictEqual(cmd.rows, 2);\n      assert.strictEqual(cmd.more, 1);\n      assert.strictEqual(cmd.quiet, 2);\n    });\n\n    it('should handle empty control data', () => {\n      const cmd = parseKittyCommand('');\n      assert.strictEqual(cmd.action, undefined);\n      assert.strictEqual(cmd.format, undefined);\n    });\n\n    it('should parse transmit action', () => {\n      const cmd = parseKittyCommand('a=t,f=100');\n      assert.strictEqual(cmd.action, KittyAction.TRANSMIT);\n      assert.strictEqual(cmd.format, KittyFormat.PNG);\n    });\n\n    it('should parse delete action', () => {\n      const cmd = parseKittyCommand('a=d,i=5');\n      assert.strictEqual(cmd.action, KittyAction.DELETE);\n      assert.strictEqual(cmd.id, 5);\n    });\n\n    it('should parse empty action as empty string', () => {\n      const cmd = parseKittyCommand('a=,f=100');\n      assert.strictEqual(cmd.action, '');\n      assert.strictEqual(cmd.format, 100);\n    });\n\n    it('should leave action undefined when key is not present', () => {\n      const cmd = parseKittyCommand('f=100,i=5');\n      assert.strictEqual(cmd.action, undefined);\n      assert.strictEqual(cmd.format, 100);\n      assert.strictEqual(cmd.id, 5);\n    });\n\n    it('should parse compression key', () => {\n      const cmd = parseKittyCommand('a=t,f=32,o=z');\n      assert.strictEqual(cmd.action, 't');\n      assert.strictEqual(cmd.format, 32);\n      assert.strictEqual(cmd.compression, 'z');\n    });\n\n    it('should parse cursor movement key', () => {\n      const cmd = parseKittyCommand('a=T,f=100,C=1');\n      assert.strictEqual(cmd.cursorMovement, 1);\n    });\n\n    it('should parse cursor movement key C=0', () => {\n      const cmd = parseKittyCommand('a=T,f=100,C=0');\n      assert.strictEqual(cmd.cursorMovement, 0);\n    });\n\n    it('should parse x and y offset', () => {\n      const cmd = parseKittyCommand('a=T,x=10,y=20');\n      assert.strictEqual(cmd.x, 10);\n      assert.strictEqual(cmd.y, 20);\n    });\n\n    it('should handle keys without values', () => {\n      const cmd = parseKittyCommand('a=t,f=,i=5');\n      assert.strictEqual(cmd.action, 't');\n      assert.ok(isNaN(cmd.format!));\n      assert.strictEqual(cmd.id, 5);\n    });\n\n    it('should parse z-index key with positive value', () => {\n      const cmd = parseKittyCommand('a=T,f=100,z=10');\n      assert.strictEqual(cmd.zIndex, 10);\n    });\n\n    it('should parse z-index key with zero', () => {\n      const cmd = parseKittyCommand('a=T,f=100,z=0');\n      assert.strictEqual(cmd.zIndex, 0);\n    });\n\n    it('should parse z-index key with negative value', () => {\n      const cmd = parseKittyCommand('a=T,f=100,z=-1');\n      assert.strictEqual(cmd.zIndex, -1);\n    });\n\n    it('should leave zIndex undefined when not specified', () => {\n      const cmd = parseKittyCommand('a=T,f=100');\n      assert.strictEqual(cmd.zIndex, undefined);\n    });\n\n    it('should parse delete selector key', () => {\n      const cmd = parseKittyCommand('a=d,d=i,i=5');\n      assert.strictEqual(cmd.action, 'd');\n      assert.strictEqual(cmd.deleteSelector, 'i');\n      assert.strictEqual(cmd.id, 5);\n    });\n\n    it('should parse uppercase delete selector', () => {\n      const cmd = parseKittyCommand('a=d,d=A');\n      assert.strictEqual(cmd.deleteSelector, 'A');\n    });\n\n    it('should parse delete selector d=a (all)', () => {\n      const cmd = parseKittyCommand('a=d,d=a');\n      assert.strictEqual(cmd.deleteSelector, 'a');\n    });\n\n    it('should leave deleteSelector undefined when not specified', () => {\n      const cmd = parseKittyCommand('a=d,i=5');\n      assert.strictEqual(cmd.deleteSelector, undefined);\n    });\n\n    it('should parse placement id key', () => {\n      const cmd = parseKittyCommand('a=d,d=i,i=5,p=3');\n      assert.strictEqual(cmd.placementId, 3);\n      assert.strictEqual(cmd.deleteSelector, 'i');\n      assert.strictEqual(cmd.id, 5);\n    });\n\n    it('should leave placementId undefined when not specified', () => {\n      const cmd = parseKittyCommand('a=d,d=i,i=5');\n      assert.strictEqual(cmd.placementId, undefined);\n    });\n\n    it('should parse image number key', () => {\n      const cmd = parseKittyCommand('a=t,f=100,I=42');\n      assert.strictEqual(cmd.imageNumber, 42);\n    });\n  });\n});\n"
  },
  {
    "path": "addons/addon-image/src/kitty/KittyGraphicsTypes.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Kitty graphics protocol types, constants, and parsing utilities.\n */\n\nimport type Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';\n\n// Kitty graphics protocol action types.\n// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference under key 'a'.\nexport const enum KittyAction {\n  TRANSMIT = 't',\n  TRANSMIT_DISPLAY = 'T',\n  QUERY = 'q',\n  PLACEMENT = 'p',\n  DELETE = 'd'\n}\n\n// Kitty graphics protocol format types.\n// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference\nexport const enum KittyFormat {\n  RGB = 24,\n  RGBA = 32,\n  PNG = 100\n}\n\n// Kitty graphics protocol compression types.\n// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference under key 'o'.\nexport const enum KittyCompression {\n  NONE = '',\n  ZLIB = 'z'\n}\n\n// Kitty graphics protocol control data keys.\n// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference\nexport const enum KittyKey {\n  // Action to perform (t=transmit, T=transmit+display, q=query, p=placement, d=delete)\n  ACTION = 'a',\n  // Image format (24=RGB, 32=RGBA, 100=PNG)\n  FORMAT = 'f',\n  // Image ID for referencing stored images\n  ID = 'i',\n  // Image number (alternative to ID, terminal assigns ID)\n  IMAGE_NUMBER = 'I',\n  // Source image width in pixels\n  WIDTH = 's',\n  // Source image height in pixels\n  HEIGHT = 'v',\n  // The left edge (in pixels) of the image area to display\n  X_OFFSET = 'x',\n  // The top edge (in pixels) of the image area to display\n  Y_OFFSET = 'y',\n  // Width (in pixels) of the source rectangle to display\n  SOURCE_WIDTH = 'w',\n  // Height (in pixels) of the source rectangle to display\n  SOURCE_HEIGHT = 'h',\n  // Horizontal offset (in pixels) within the first cell\n  X_PLACEMENT_OFFSET = 'X',\n  // Vertical offset (in pixels) within the first cell\n  Y_PLACEMENT_OFFSET = 'Y',\n  // Number of terminal columns to display the image over\n  COLUMNS = 'c',\n  // Number of terminal rows to display the image over\n  ROWS = 'r',\n  // More data flag (1=more chunks coming, 0=final chunk)\n  MORE = 'm',\n  // Compression type (z=zlib). This is essential for chunking larger images.\n  COMPRESSION = 'o',\n  // Quiet mode (1=suppress OK responses, 2=suppress error responses)\n  QUIET = 'q',\n  // Cursor movement policy (0=move cursor after image, 1=don't move cursor)\n  CURSOR_MOVEMENT = 'C',\n  // Z-index for image layering (negative = behind text, 0+ = on top)\n  Z_INDEX = 'z',\n  // Transmission medium (d=direct, f=file, t=temp file, s=shared memory)\n  TRANSMISSION = 't',\n  // Delete selector (a/A=all, i/I=by id, c/C=at cursor, etc.) — only used when a=d\n  DELETE_SELECTOR = 'd',\n  // Placement ID for targeting specific placements\n  PLACEMENT_ID = 'p'\n}\n\n// Pixel format constants\nexport const BYTES_PER_PIXEL_RGB = 3;\nexport const BYTES_PER_PIXEL_RGBA = 4;\nexport const ALPHA_OPAQUE = 255;\n\n// Parsed Kitty graphics command.\nexport interface IKittyCommand {\n  action?: string;\n  format?: number;\n  id?: number;\n  imageNumber?: number;\n  width?: number;\n  height?: number;\n  x?: number;\n  y?: number;\n  sourceWidth?: number;\n  sourceHeight?: number;\n  xOffset?: number;\n  yOffset?: number;\n  columns?: number;\n  rows?: number;\n  more?: number;\n  quiet?: number;\n  cursorMovement?: number;\n  zIndex?: number;\n  transmission?: string;\n  deleteSelector?: string;\n  placementId?: number;\n  compression?: string;\n  payload?: string;\n}\n\n// Pending chunked transmission state.\n// Stores metadata from the first chunk while accumulating decoded payload data.\nexport interface IPendingTransmission {\n  // The parsed command from the first chunk (contains action, format, dimensions, etc.)\n  cmd: IKittyCommand;\n  // Decoder used across chunked payloads\n  decoder: Base64Decoder;\n  // Total encoded (base64) bytes received across all chunks - for size limit enforcement\n  totalEncodedSize: number;\n  // Whether any chunk has failed to decode\n  decodeError: boolean;\n}\n\n// Stored Kitty image data.\nexport interface IKittyImageData {\n  id: number;\n  // Decoded image data stored as Blob (off JS heap) to avoid 2GB heap limit\n  data: Blob;\n  width: number;\n  height: number;\n  format: 24 | 32 | 100;\n  compression?: string;\n}\n\n// Parses Kitty graphics control data into a command object.\nexport function parseKittyCommand(data: string): IKittyCommand {\n  const cmd: IKittyCommand = {};\n  const parts = data.split(',');\n\n  for (const part of parts) {\n    const eqIdx = part.indexOf('=');\n    if (eqIdx === -1) continue;\n\n    const key = part.substring(0, eqIdx);\n    const value = part.substring(eqIdx + 1);\n\n    // Handle string keys first\n    if (key === KittyKey.ACTION) {\n      cmd.action = value;\n      continue;\n    }\n    if (key === KittyKey.COMPRESSION) {\n      cmd.compression = value;\n      continue;\n    }\n    if (key === KittyKey.TRANSMISSION) {\n      cmd.transmission = value;\n      continue;\n    }\n    if (key === KittyKey.DELETE_SELECTOR) {\n      cmd.deleteSelector = value;\n      continue;\n    }\n    const numValue = parseInt(value);\n    switch (key) {\n      case KittyKey.FORMAT: cmd.format = numValue; break;\n      case KittyKey.ID: cmd.id = numValue; break;\n      case KittyKey.IMAGE_NUMBER: cmd.imageNumber = numValue; break;\n      case KittyKey.WIDTH: cmd.width = numValue; break;\n      case KittyKey.HEIGHT: cmd.height = numValue; break;\n      case KittyKey.X_OFFSET: cmd.x = numValue; break;\n      case KittyKey.Y_OFFSET: cmd.y = numValue; break;\n      case KittyKey.SOURCE_WIDTH: cmd.sourceWidth = numValue; break;\n      case KittyKey.SOURCE_HEIGHT: cmd.sourceHeight = numValue; break;\n      case KittyKey.X_PLACEMENT_OFFSET: cmd.xOffset = numValue; break;\n      case KittyKey.Y_PLACEMENT_OFFSET: cmd.yOffset = numValue; break;\n      case KittyKey.COLUMNS: cmd.columns = numValue; break;\n      case KittyKey.ROWS: cmd.rows = numValue; break;\n      case KittyKey.MORE: cmd.more = numValue; break;\n      case KittyKey.QUIET: cmd.quiet = numValue; break;\n      case KittyKey.CURSOR_MOVEMENT: cmd.cursorMovement = numValue; break;\n      case KittyKey.Z_INDEX: cmd.zIndex = numValue; break;\n      case KittyKey.PLACEMENT_ID: cmd.placementId = numValue; break;\n    }\n  }\n\n  return cmd;\n}\n"
  },
  {
    "path": "addons/addon-image/src/kitty/KittyImageStorage.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from '@xterm/xterm';\nimport { ImageStorage } from '../ImageStorage';\nimport { ImageLayer } from '../Types';\nimport { IKittyImageData } from './KittyGraphicsTypes';\n\n// Kitty-specific image storage controller.\n//\n// Wraps shared ImageStorage with kitty protocol semantics:\n// - tracks transmitted image payloads by kitty image id\n// - tracks kitty image id -> shared ImageStorage id mapping for displayed images\n// - mirrors shared-storage evictions into kitty maps\n// - applies protocol-level undisplayed-image eviction policy\nexport class KittyImageStorage implements IDisposable {\n  private static readonly _maxStoredImages = 256;\n\n  private _nextImageId = 1;\n  private readonly _images: Map<number, IKittyImageData> = new Map();\n  // TODO: Support multiple placements per image. The kitty spec identifies\n  // placements by an (image id, placement id) pair — same i + different p\n  // values should coexist, and same i + same p should replace the prior\n  // placement. Currently we track only one storage entry per kitty image id,\n  // so multiple placements of the same image overwrite each other. Fixing\n  // this requires changing these maps to Map<number, Map<number, number>>\n  // (kittyId → placementId → storageId) and updating addImage/deleteById\n  // accordingly. The underlying shared ImageStorage would also need to\n  // support multiple entries per logical image.\n  private readonly _kittyIdToStorageId: Map<number, number> = new Map();\n  private readonly _storageIdToKittyId: Map<number, number> = new Map();\n\n  private readonly _previousOnImageDeleted: ((storageId: number) => void) | undefined;\n  private readonly _wrappedOnImageDeleted: (storageId: number) => void;\n  private readonly _handleStorageImageDeleted = (storageId: number): void => {\n    const kittyId = this._storageIdToKittyId.get(storageId);\n    if (kittyId !== undefined) {\n      this._kittyIdToStorageId.delete(kittyId);\n      this._storageIdToKittyId.delete(storageId);\n      this._images.delete(kittyId);\n    }\n  };\n\n  constructor(\n    private readonly _storage: ImageStorage\n  ) {\n    this._previousOnImageDeleted = this._storage.onImageDeleted;\n    this._wrappedOnImageDeleted = (storageId: number) => {\n      this._previousOnImageDeleted?.(storageId);\n      this._handleStorageImageDeleted(storageId);\n    };\n    this._storage.onImageDeleted = this._wrappedOnImageDeleted;\n  }\n\n  public reset(): void {\n    this._nextImageId = 1;\n    this._images.clear();\n    this._kittyIdToStorageId.clear();\n    this._storageIdToKittyId.clear();\n  }\n\n  public dispose(): void {\n    this.reset();\n    if (this._storage.onImageDeleted === this._wrappedOnImageDeleted) {\n      this._storage.onImageDeleted = this._previousOnImageDeleted;\n    }\n  }\n\n  public storeImage(id: number | undefined, imageData: Omit<IKittyImageData, 'id'>): number {\n    const imageId = id ?? this._nextImageId++;\n\n    const oldStorageId = this._kittyIdToStorageId.get(imageId);\n    if (oldStorageId !== undefined) {\n      this._storage.deleteImage(oldStorageId);\n      this._kittyIdToStorageId.delete(imageId);\n      this._storageIdToKittyId.delete(oldStorageId);\n    }\n\n    if (!this._images.has(imageId) && this._images.size >= KittyImageStorage._maxStoredImages) {\n      this._evictUndisplayedImages();\n    }\n\n    this._images.set(imageId, {\n      ...imageData,\n      id: imageId\n    });\n    return imageId;\n  }\n\n  public addImage(kittyId: number, image: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer, zIndex: number): void {\n    // Clean up stale reverse-mapping from a previous placement of the same\n    // kitty image.  The old shared-storage entry is kept (it may still be\n    // visible on screen) but its reverse mapping is removed so that eviction\n    // of the old entry won't incorrectly delete the kitty image data.\n    const oldStorageId = this._kittyIdToStorageId.get(kittyId);\n    if (oldStorageId !== undefined) {\n      this._storageIdToKittyId.delete(oldStorageId);\n    }\n    const storageId = this._storage.addImage(image, scrolling, layer, zIndex);\n    this._kittyIdToStorageId.set(kittyId, storageId);\n    this._storageIdToKittyId.set(storageId, kittyId);\n  }\n\n  public getImage(kittyId: number): IKittyImageData | undefined {\n    return this._images.get(kittyId);\n  }\n\n  public deleteById(kittyId: number): void {\n    this._images.delete(kittyId);\n    const storageId = this._kittyIdToStorageId.get(kittyId);\n    if (storageId !== undefined) {\n      this._storage.deleteImage(storageId);\n      this._kittyIdToStorageId.delete(kittyId);\n      this._storageIdToKittyId.delete(storageId);\n    }\n  }\n\n  public deleteAll(): void {\n    this._images.clear();\n    for (const storageId of this._kittyIdToStorageId.values()) {\n      this._storage.deleteImage(storageId);\n    }\n    this._kittyIdToStorageId.clear();\n    this._storageIdToKittyId.clear();\n  }\n\n  public get images(): ReadonlyMap<number, IKittyImageData> {\n    return this._images;\n  }\n\n  public get kittyIdToStorageId(): ReadonlyMap<number, number> {\n    return this._kittyIdToStorageId;\n  }\n\n  public get lastImageId(): number {\n    return this._nextImageId - 1;\n  }\n\n  private _evictUndisplayedImages(): void {\n    for (const [kittyId] of this._images) {\n      if (this._images.size <= KittyImageStorage._maxStoredImages / 2) {\n        break;\n      }\n      if (!this._kittyIdToStorageId.has(kittyId)) {\n        this._images.delete(kittyId);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"sourceMap\": true,\n    \"outDir\": \"../out\",\n    \"rootDir\": \".\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"preserveWatchOutput\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"browser/*\": [ \"../../../src/browser/*\" ],\n      \"common/*\": [ \"../../../src/common/*\" ],\n      \"@xterm/addon-image\": [ \"../typings/addon-image.d.ts\" ],\n      \"*\": [ \"./*\" ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    { \"path\": \"../../../src/browser\" },\n    { \"path\": \"../../../src/common\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-image/test/ImageAddon.test.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { FINALIZER, introducer, sixelEncode } from 'sixel';\nimport { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';\nimport { deepStrictEqual, ok, strictEqual } from 'assert';\n\n/**\n * Plugin ctor options.\n */\nexport interface IImageAddonOptions {\n  enableSizeReports: boolean;\n  pixelLimit: number;\n  storageLimit: number;\n  showPlaceholder: boolean;\n  sixelSupport: boolean;\n  sixelScrolling: boolean;\n  sixelPaletteLimit: number;\n  sixelSizeLimit: number;\n  iipSupport: boolean;\n  iipSizeLimit: number;\n  kittySupport: boolean;\n  kittySizeLimit: number;\n}\n\n// eslint-disable-next-line\ndeclare const ImageAddon: {\n  new(options?: Partial<IImageAddonOptions>): any;\n};\n\ninterface ITestData {\n  width: number;\n  height: number;\n  bytes: Uint8Array;\n  palette: number[];\n  sixel: string;\n}\n\ninterface IDimensions {\n  cellWidth: number;\n  cellHeight: number;\n  width: number;\n  height: number;\n}\n\n// image: 640 x 80, 512 color\nconst TESTDATA: ITestData = (() => {\n  const data8 = readFileSync('./addons/addon-image/fixture/palette.blob');\n  const data32 = new Uint32Array(data8.buffer);\n  const palette = new Set<number>();\n  for (let i = 0; i < data32.length; ++i) palette.add(data32[i]);\n  const sixel = sixelEncode(data8, 640, 80, [...palette]);\n  return {\n    width: 640,\n    height: 80,\n    bytes: data8,\n    palette: [...palette],\n    sixel\n  };\n})();\nconst SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER;\n// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;\n// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;\n\n// NOTE: the data is loaded as string for easier transport through playwright\nconst TESTDATA_IIP: [string, [number, number]][] = [\n  [readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]],\n  [readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]],\n  [readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]],\n  [readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]],\n  [readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]]\n];\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx, { cols: 80, rows: 24 });\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('ImageAddon', () => {\n\n  test.beforeEach(async ({}, testInfo) => {\n    // DEBT: This test never worked on webkit\n    if (ctx.browser.browserType().name() === 'webkit') {\n      testInfo.skip();\n      return;\n    }\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.imageAddon?.dispose();\n      window.imageAddon = new ImageAddon({ sixelPaletteLimit: 512 });\n      window.term.loadAddon(window.imageAddon);\n    `);\n  });\n\n  test('test for private accessors', async () => {\n    // terminal privates\n    const accessors = [\n      '_core',\n      '_core._renderService',\n      '_core._inputHandler',\n      '_core._inputHandler._parser',\n      '_core._inputHandler._curAttrData',\n      '_core._inputHandler._dirtyRowTracker',\n      '_core._themeService.colors',\n      '_core._coreBrowserService'\n    ];\n    for (const prop of accessors) {\n      strictEqual(\n        await ctx.page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'),\n        true, `problem at ${prop}`\n      );\n    }\n    // bufferline privates\n    strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);\n    strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);\n    // inputhandler privates\n    strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), '_AttributeData');\n    strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');\n  });\n\n  test.describe('ctor options', () => {\n    test('empty settings should load defaults', async () => {\n      const DEFAULT_OPTIONS: IImageAddonOptions = {\n        enableSizeReports: true,\n        pixelLimit: 16777216,\n        sixelSupport: true,\n        sixelScrolling: true,\n        sixelPaletteLimit: 512,  // set to 512 to get example image working\n        sixelSizeLimit: 25000000,\n        storageLimit: 128,\n        showPlaceholder: true,\n        iipSupport: true,\n        iipSizeLimit: 20000000,\n        kittySupport: true,\n        kittySizeLimit: 20000000\n      };\n      deepStrictEqual(await ctx.page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS);\n    });\n    test('custom settings should overload defaults', async () => {\n      const customSettings: IImageAddonOptions = {\n        enableSizeReports: false,\n        pixelLimit: 5,\n        sixelSupport: false,\n        sixelScrolling: false,\n        sixelPaletteLimit: 1024,\n        sixelSizeLimit: 1000,\n        storageLimit: 10,\n        showPlaceholder: false,\n        iipSupport: false,\n        iipSizeLimit: 1000,\n        kittySupport: false,\n        kittySizeLimit: 1000\n      };\n      await ctx.page.evaluate(opts => {\n        (window as any).imageAddonCustom = new ImageAddon(opts.opts);\n        (window as any).term.loadAddon((window as any).imageAddonCustom);\n      }, { opts: customSettings });\n      deepStrictEqual(await ctx.page.evaluate(`window.imageAddonCustom._opts`), customSettings);\n    });\n  });\n\n  test.describe('scrolling & cursor modes', () => {\n    test('testdata default (scrolling with VT240 cursor pos)', async () => {\n      const dim = await getDimensions();\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);\n      // moved to right by 10 cells\n      await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);\n    });\n    test('write testdata noScrolling', async () => {\n      await ctx.proxy.write('\\x1b[?80h' + SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [0, 0]);\n      // second draw does not change anything\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [0, 0]);\n    });\n    test('testdata cursor always at VT240 pos', async () => {\n      const dim = await getDimensions();\n      // offset 0\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);\n      // moved to right by 10 cells\n      await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);\n      // moved by 30 cells (+10 prev)\n      await ctx.proxy.write('#'.repeat(30) + SIXEL_SEQ_0);\n      deepStrictEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]);\n    });\n  });\n\n  test.describe('image lifecycle & eviction', () => {\n    test('onImageAdded fires for each image', async () => {\n      await ctx.page.evaluate(`\n        window._imageAddedCount = 0;\n        window.imageAddon.onImageAdded(() => { window._imageAddedCount++; });\n      `);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await pollFor(ctx.page, 'window._imageAddedCount', 1);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await pollFor(ctx.page, 'window._imageAddedCount', 2);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await pollFor(ctx.page, 'window._imageAddedCount', 3);\n    });\n    test('delete image once scrolled off', async () => {\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);\n      // scroll to scrollback + rows - 1\n      await ctx.page.evaluate(\n        scrollback => new Promise(res => (window as any).term.write('\\n'.repeat(scrollback), res)),\n        (await getScrollbackPlusRows() - 1)\n      );\n      // wait here, as we have to make sure, that eviction did not yet occur\n      await timeout(100);\n      pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);\n      // scroll one further should delete the image\n      await ctx.page.evaluate(() => new Promise(res => (window as any).term.write('\\n', res)));\n      pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0);\n    });\n    test('get storageUsage', async () => {\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      ok(Math.abs((await ctx.page.evaluate<number>('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05);\n    });\n    test('get/set storageLimit', async () => {\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 128);\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1);\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 1);\n    });\n    test('remove images by storage limit pressure', async () => {\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1);\n      // never go beyond storage limit\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await timeout(100);\n      const usage = await ctx.page.evaluate('window.imageAddon.storageUsage');\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage);\n      strictEqual(usage as number < 1, true);\n    });\n    test('set storageLimit removes images synchronously', async () => {\n      await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);\n      const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');\n      const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageLimit = 0.5; window.imageAddon.storageUsage');\n      strictEqual(newUsage < usage, true);\n      strictEqual(newUsage < 0.5, true);\n    });\n    test('clear alternate images on buffer change', async () => {\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);\n      await ctx.proxy.write('\\x1b[?1049h' + SIXEL_SEQ_0);\n      ok(Math.abs((await ctx.page.evaluate<number>('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05);\n      await ctx.proxy.write('\\x1b[?1049l');\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);\n    });\n    test('evict tiles by in-place overwrites (only full overwrite tested)', async () => {\n      await timeout(50);\n      await ctx.proxy.write('\\x1b[H' + SIXEL_SEQ_0 + '\\x1b[100;100H');\n      await timeout(50);\n      let usage = await ctx.page.evaluate('window.imageAddon.storageUsage');\n      while (usage === 0) {\n        await timeout(50);\n        usage = await ctx.page.evaluate('window.imageAddon.storageUsage');\n      }\n      await ctx.proxy.write('\\x1b[H' + SIXEL_SEQ_0 + '\\x1b[100;100H');\n      await timeout(200); // wait some time and re-check\n      strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage);\n    });\n    test('manual eviction on alternate buffer must not miss images', async () => {\n      await ctx.proxy.write('\\x1b[?1049h');\n      await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);\n      await timeout(100);\n      const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');\n      await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);\n      await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);\n      await timeout(100);\n      const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');\n      strictEqual(newUsage, usage);\n    });\n  });\n\n  test.describe('IIP support - testimages', () => {\n    test('palette.png', async () => {\n      await ctx.proxy.write(TESTDATA_IIP[0][0]);\n      deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[0][1]);\n    });\n    test('spinfox.png', async () => {\n      await ctx.proxy.write(TESTDATA_IIP[1][0]);\n      deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);\n    });\n    test('w3c gif', async () => {\n      await ctx.proxy.write(TESTDATA_IIP[2][0]);\n      deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[2][1]);\n    });\n    test('w3c jpeg', async () => {\n      await ctx.proxy.write(TESTDATA_IIP[3][0]);\n      deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[3][1]);\n    });\n    test('w3c png', async () => {\n      await ctx.proxy.write(TESTDATA_IIP[4][0]);\n      deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[4][1]);\n    });\n  });\n});\n\n/**\n * terminal access helpers.\n */\nasync function getDimensions(): Promise<IDimensions> {\n  const dimensions: any = await ctx.page.evaluate(`term.dimensions`);\n  return {\n    cellWidth: Math.round(dimensions.css.cell.width),\n    cellHeight: Math.round(dimensions.css.cell.height),\n    width: Math.round(dimensions.css.canvas.width),\n    height: Math.round(dimensions.css.canvas.height)\n  };\n}\n\nasync function getCursor(): Promise<[number, number]> {\n  return ctx.page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]');\n}\n\nasync function getImageStorageLength(): Promise<number> {\n  return ctx.page.evaluate('window.imageAddon._storage._images.size');\n}\n\nasync function getScrollbackPlusRows(): Promise<number> {\n  return ctx.page.evaluate('window.term.options.scrollback + window.term.rows');\n}\n\nasync function getOrigSize(id: number): Promise<[number, number]> {\n  return ctx.page.evaluate<any>(`[\n    window.imageAddon._storage._images.get(${id}).orig.width,\n    window.imageAddon._storage._images.get(${id}).orig.height\n  ]`);\n}\n"
  },
  {
    "path": "addons/addon-image/test/KittyGraphics.test.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';\nimport { deepStrictEqual, ok, strictEqual } from 'assert';\n\n/**\n * Plugin ctor options.\n */\nexport interface IImageAddonOptions {\n  enableSizeReports: boolean;\n  pixelLimit: number;\n  storageLimit: number;\n  showPlaceholder: boolean;\n  sixelSupport: boolean;\n  sixelScrolling: boolean;\n  sixelPaletteLimit: number;\n  sixelSizeLimit: number;\n  iipSupport: boolean;\n  iipSizeLimit: number;\n  kittySupport: boolean;\n  kittySizeLimit: number;\n}\n\n// eslint-disable-next-line\ndeclare const ImageAddon: {\n  new(options?: Partial<IImageAddonOptions>): any;\n};\n\ninterface IDimensions {\n  cellWidth: number;\n  cellHeight: number;\n  width: number;\n  height: number;\n}\n\n// Kitty graphics test images\nconst KITTY_BLACK_1X1_BASE64 = readFileSync('./addons/addon-image/fixture/kitty/black-1x1.png').toString('base64');\nconst KITTY_BLACK_1X1_BYTES = Array.from(readFileSync('./addons/addon-image/fixture/kitty/black-1x1.png'));\nconst KITTY_RGB_3X1_BASE64 = readFileSync('./addons/addon-image/fixture/kitty/rgb-3x1.png').toString('base64');\nconst KITTY_MULTICOLOR_200X100_BASE64 = readFileSync('./addons/addon-image/fixture/kitty/multicolor-200x100.png').toString('base64');\nconst KITTY_MULTICOLOR_200X100_BYTES = Array.from(readFileSync('./addons/addon-image/fixture/kitty/multicolor-200x100.png'));\n\n// Raw RGB pixel data (f=24): 3 bytes per pixel, no header — requires s= and v=\nconst RAW_RGB_1X1_BLACK = Buffer.from([0, 0, 0]).toString('base64');\nconst RAW_RGB_1X1_RED = Buffer.from([255, 0, 0]).toString('base64');\nconst RAW_RGB_3X1 = Buffer.from([\n  255, 0, 0,\n  0, 255, 0,\n  0, 0, 255\n]).toString('base64');\nconst RAW_RGB_2X2 = Buffer.from([\n  255, 0, 0,    0, 255, 0,\n  0, 0, 255,    255, 255, 0\n]).toString('base64');\n// 5 pixels (1 uint32 block + 1 remainder) — tests block+tail boundary\nconst RAW_RGB_5X1 = Buffer.from([\n  255, 0, 0,\n  0, 255, 0,\n  0, 0, 255,\n  255, 255, 0,\n  255, 0, 255\n]).toString('base64');\n// 8 pixels (2 full uint32 blocks, 0 remainder) — tests multi-block path\nconst RAW_RGB_4X2 = Buffer.from([\n  255, 0, 0,    0, 255, 0,    0, 0, 255,    255, 255, 0,\n  255, 0, 255,  0, 255, 255,  128, 128, 128, 255, 255, 255\n]).toString('base64');\n\n// Raw RGBA pixel data (f=32): 4 bytes per pixel, no header — requires s= and v=\nconst RAW_RGBA_1X1_WHITE = Buffer.from([255, 255, 255, 255]).toString('base64');\nconst RAW_RGBA_1X1_RED = Buffer.from([255, 0, 0, 255]).toString('base64');\nconst RAW_RGBA_1X1_TRANSPARENT = Buffer.from([0, 0, 0, 0]).toString('base64');\nconst RAW_RGBA_3X1 = Buffer.from([\n  255, 0, 0, 255,\n  0, 255, 0, 255,\n  0, 0, 255, 255\n]).toString('base64');\nconst RAW_RGBA_2X2 = Buffer.from([\n  255, 0, 0, 255,    0, 255, 0, 255,\n  0, 0, 255, 255,    255, 255, 0, 255\n]).toString('base64');\n// 5 pixels — tests RGBA zero-copy with non-power-of-2 count\nconst RAW_RGBA_5X1 = Buffer.from([\n  255, 0, 0, 255,\n  0, 255, 0, 255,\n  0, 0, 255, 255,\n  255, 255, 0, 255,\n  255, 0, 255, 255\n]).toString('base64');\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx, { cols: 80, rows: 24 });\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('Kitty Graphics Protocol', () => {\n  // TODO: Add tests for larger images with various dimensions\n  // TODO: Add tests for virtual placement (U=1)\n  // TODO: Add tests for animation frames\n  // TODO: Add performance tests for streaming large images\n  // TODO: Implement cursor movement per Kitty spec - cursor should move by cols/rows after placement (unless C=1)\n  // TODO: Distinguish lowercase delete selectors (placement only) from uppercase (placement + free data)\n\n  test.beforeEach(async ({}, testInfo) => {\n    // DEBT: This test never worked on webkit\n    if (ctx.browser.browserType().name() === 'webkit') {\n      testInfo.skip();\n      return;\n    }\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.imageAddon?.dispose();\n      window.imageAddon = new ImageAddon({ sixelPaletteLimit: 512 });\n      window.term.loadAddon(window.imageAddon);\n    `);\n  });\n\n  test.describe('Basic transmission and storage', () => {\n    test('stores 1x1 black PNG with a=T (transmit and display)', async () => {\n      const seq = `\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      deepStrictEqual(await getOrigSize(1), [1, 1]);\n    });\n\n    test('stores 3x1 RGB PNG with a=T', async () => {\n      const seq = `\\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      deepStrictEqual(await getOrigSize(1), [3, 1]);\n    });\n\n    test('transmit only (a=t) does not display but stores in handler', async () => {\n      const seq = `\\x1b_Ga=t,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n    });\n\n    test('uses specified image ID', async () => {\n      const seq = `\\x1b_Ga=t,f=100,i=42;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(42)`), true);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(1)`), false);\n    });\n\n    test('assigns auto-incrementing IDs when not specified', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 2);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(1)`), true);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(2)`), true);\n    });\n\n    test('defaults to transmit action when action is omitted', async () => {\n      const seq = `\\x1b_Gf=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n    });\n\n    test('ignores command when action is empty string', async () => {\n      const seq = `\\x1b_Ga=,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 0);\n    });\n  });\n\n  test.describe('Chunked transmission', () => {\n    test('handles chunked transmission (m=1)', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      const seq1 = `\\x1b_Ga=T,f=100,i=99,m=1;${part1}\\x1b\\\\`;\n      const seq2 = `\\x1b_Ga=T,f=100,i=99;${part2}\\x1b\\\\`;\n\n      await ctx.proxy.write(seq1);\n      await timeout(50);\n      strictEqual(await getImageStorageLength(), 0);\n\n      await ctx.proxy.write(seq2);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n    });\n\n    test('verifies chunked data is assembled correctly', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=99,m=1;${part1}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=99;${part2}\\x1b\\\\`);\n      await timeout(100);\n\n      const storedData = await ctx.page.evaluate(async () => {\n        const blob = (window as any).imageAddon._handlers.get('kitty').images.get(99).data;\n        const buffer = await blob.arrayBuffer();\n        return Array.from(new Uint8Array(buffer));\n      });\n      deepStrictEqual(storedData, KITTY_BLACK_1X1_BYTES);\n    });\n\n    test('enforces size limit across chunked transmissions', async () => {\n      // Create a custom addon with very small size limit (100 bytes)\n      // The 1x1 PNG is ~164 bytes base64, so 2 chunks should exceed 100\n      await ctx.page.evaluate(() => {\n        (window as any).smallLimitAddon = new ImageAddon({\n          kittySupport: true,\n          kittySizeLimit: 100  // Very small limit\n        });\n        (window as any).term.loadAddon((window as any).smallLimitAddon);\n      });\n\n      // Split the base64 data into two chunks\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      // Send chunked data - first chunk (~82 bytes) is under limit\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=777,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n\n      // Second chunk brings total to ~164 bytes, exceeding 100 byte limit\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=777;${part2}\\x1b\\\\`);\n      await timeout(100);\n\n      // Image should NOT be stored due to size limit\n      strictEqual(await ctx.page.evaluate(`window.smallLimitAddon._handlers.get('kitty').images.has(777)`), false);\n\n      // Cleanup\n      await ctx.page.evaluate(() => {\n        (window as any).smallLimitAddon.dispose();\n      });\n    });\n\n    test('chunked a=T works when subsequent chunks omit i= (spec pattern)', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=400,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await getImageStorageLength(), 0);\n\n      await ctx.proxy.write(`\\x1b_Gm=0;${part2}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n    });\n\n    test('chunked a=t works when subsequent chunks omit i= (spec pattern)', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=401,m=1;${part1}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Gm=0;${part2}\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(401)`), true);\n    });\n\n    test('chunked data without i= on subsequent chunks is assembled correctly', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=402,m=1;${part1}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Gm=0;${part2}\\x1b\\\\`);\n      await timeout(100);\n\n      const storedData = await ctx.page.evaluate(async () => {\n        const blob = (window as any).imageAddon._handlers.get('kitty').images.get(402).data;\n        const buffer = await blob.arrayBuffer();\n        return Array.from(new Uint8Array(buffer));\n      });\n      deepStrictEqual(storedData, KITTY_BLACK_1X1_BYTES);\n    });\n\n    test('three-chunk transfer with only m= on middle and last chunks', async () => {\n      const third = Math.floor(KITTY_BLACK_1X1_BASE64.length / 3);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, third);\n      const part2End = third + Math.floor((KITTY_BLACK_1X1_BASE64.length - third) / 2);\n      const alignedPart2End = part2End - (part2End - third) % 4 + third;\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(third, alignedPart2End);\n      const part3 = KITTY_BLACK_1X1_BASE64.substring(alignedPart2End);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=403,m=1;${part1}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Gm=1;${part2}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Gm=0;${part3}\\x1b\\\\`);\n      await timeout(100);\n\n      const storedData = await ctx.page.evaluate(async () => {\n        const blob = (window as any).imageAddon._handlers.get('kitty').images.get(403).data;\n        const buffer = await blob.arrayBuffer();\n        return Array.from(new Uint8Array(buffer));\n      });\n      deepStrictEqual(storedData, KITTY_BLACK_1X1_BYTES);\n    });\n\n    test('chunked a=T without i= on any chunk works (no response)', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await getImageStorageLength(), 0);\n\n      await ctx.proxy.write(`\\x1b_Gm=0;${part2}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n    });\n\n    test('chunked transfer responds OK on final chunk when i= on first only', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=405,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n\n      let response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '');\n\n      await ctx.proxy.write(`\\x1b_Gm=0;${part2}\\x1b\\\\`);\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=405;OK\\x1b\\\\');\n    });\n  });\n\n  test.describe('Delete commands', () => {\n    test('delete command (a=d,d=i) removes specific image by id', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=10;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=10\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 0);\n    });\n\n    test('delete command (a=d) removes all images when no id specified', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=2;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 0);\n    });\n\n    test('delete by id aborts in-flight chunked upload', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=50,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=50\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 0);\n    });\n\n    test('delete by id only aborts targeted upload, not others', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=55,m=1;${part1}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=56,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=55\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.has(56)`), true);\n    });\n\n    test('delete all aborts in-flight chunked upload', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=60,m=1;${part1}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=61,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 0);\n    });\n\n    test('d=i selector deletes specific image by id', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=80;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=81;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=80\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(81)`), true);\n    });\n\n    test('d=I selector deletes specific image by id (uppercase)', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=82;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=83;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=I,i=82\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(83)`), true);\n    });\n\n    test('d=a selector deletes all images', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=84;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=85;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=a\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 0);\n    });\n\n    test('d=A selector deletes all images (uppercase)', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=86;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=87;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 2);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=A\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 0);\n    });\n\n    test('d=a selector also removes displayed images from storage', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=88;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=a\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await getImageStorageLength(), 0);\n    });\n\n    test('d=i selector also removes displayed image from storage', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=89;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=89\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await getImageStorageLength(), 0);\n    });\n\n    test('d=i without id does nothing', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=90;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n    });\n\n    test('d=i selector clears pixels from canvas', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=92,q=1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 255]);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=92\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getPixel(0, 0, 0, 0), null);\n    });\n\n    test('d=a selector clears all pixels from canvas', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=93,q=1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 255]);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=a\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getPixel(0, 0, 0, 0), null);\n    });\n\n    test('unsupported delete selector is ignored', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=91;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d,d=c\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n    });\n\n    test('chunks sent after delete are not assembled with previous data', async () => {\n      const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);\n      const part1 = KITTY_BLACK_1X1_BASE64.substring(0, half);\n      const part2 = KITTY_BLACK_1X1_BASE64.substring(half);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=70,m=1;${part1}\\x1b\\\\`);\n      await timeout(50);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').pendingTransmissions.size`), 1);\n\n      await ctx.proxy.write(`\\x1b_Ga=d\\x1b\\\\`);\n      await timeout(50);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=70;${part2}\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(70)`), true);\n      const storedSize: number = await ctx.page.evaluate(async () => {\n        const blob = (window as any).imageAddon._handlers.get('kitty').images.get(70).data;\n        return blob.size;\n      });\n      ok(storedSize < KITTY_BLACK_1X1_BYTES.length, 'stored data should be smaller than full image (only second half)');\n    });\n  });\n\n  test.describe('Query support (a=q)', () => {\n    test('responds with OK for capability query without payload', async () => {\n      let response = '';\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=31,a=q;\\x1b\\\\');\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=31;OK\\x1b\\\\');\n    });\n\n    test('responds with OK for valid PNG query', async () => {\n      let response = '';\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=42;OK\\x1b\\\\');\n    });\n\n    test('query does NOT store the image (unlike transmit)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).term.onData(() => { /* consume response */ });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=50,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(50)`), false);\n    });\n\n    test('responds with error for invalid base64', async () => {\n      let response = '';\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=60,a=q,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=60;EINVAL:'), true);\n    });\n\n    test('responds with error for RGB data without dimensions', async () => {\n      let response = '';\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=70,a=q,f=24;AAAA\\x1b\\\\');\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=70;EINVAL:width and height required for raw pixel data\\x1b\\\\');\n    });\n\n    test('suppresses OK response when q=1', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=80,a=q,q=1,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('suppresses error response when q=2', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=90,a=q,q=2,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('responds with EINVAL when both i and I keys are specified', async () => {\n      let response = '';\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      // Per spec: \"Specifying both i and I keys in any command is an error\"\n      await ctx.proxy.write(`\\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=100;EINVAL:cannot specify both i and I keys\\x1b\\\\');\n    });\n\n    test('responds with EINVAL for i+I conflict even without payload', async () => {\n      let response = '';\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      // Delete command with both i and I (no payload case)\n      await ctx.proxy.write('\\x1b_Gi=101,I=201,a=d\\x1b\\\\');\n      await timeout(100);\n\n      response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=101;EINVAL:cannot specify both i and I keys\\x1b\\\\');\n    });\n  });\n\n  test.describe('Error responses for transmit and display', () => {\n    test('a=t sends EINVAL on decode error when id is specified', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=110,a=t,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=110;EINVAL:invalid base64 data\\x1b\\\\');\n    });\n\n    test('a=t sends no response on decode error without id', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write('\\x1b_Ga=t,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('a=T sends EINVAL on decode error when id is specified', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=120,a=T,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=120;EINVAL:invalid base64 data\\x1b\\\\');\n    });\n\n    test('a=T sends no response on decode error without id', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write('\\x1b_Ga=T,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('a=T sends EINVAL when raw pixel render fails (missing dimensions)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=130,a=T,f=24;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=130;EINVAL:'), true);\n    });\n\n    test('a=T sends OK on successful render with id', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=140,a=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=140;OK\\x1b\\\\');\n    });\n\n    test('a=t sends OK on successful transmit with id', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=150,a=t,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=150;OK\\x1b\\\\');\n    });\n\n    test('a=t EINVAL suppressed by q=2', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=160,a=t,q=2,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('a=T EINVAL suppressed by q=2', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write('\\x1b_Gi=170,a=T,q=2,f=100;!!!invalid!!!\\x1b\\\\');\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('a=t OK suppressed by q=1', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=180,a=t,q=1,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('a=T OK suppressed by q=1', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=190,a=T,q=1,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n  });\n\n  test.describe('Transmission medium rejection', () => {\n    test('query rejects t=f (file transmission)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=200,a=q,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=200;EINVAL:'), true);\n    });\n\n    test('query rejects t=s (shared memory)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=201,a=q,t=s,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=201;EINVAL:'), true);\n    });\n\n    test('query rejects t=t (temp file)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=202,a=q,t=t,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=202;EINVAL:'), true);\n    });\n\n    test('query accepts t=d (direct transmission)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=203,a=q,t=d,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=203;OK\\x1b\\\\');\n    });\n\n    test('query without t key defaults to direct (OK)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=204,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=204;OK\\x1b\\\\');\n    });\n\n    test('transmit rejects t=f with id (EINVAL response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=300,a=t,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=300;EINVAL:'), true);\n    });\n\n    test('transmit rejects t=s with id (EINVAL response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=301,a=t,t=s,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=301;EINVAL:'), true);\n    });\n\n    test('transmit rejects t=t with id (EINVAL response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=302,a=t,t=t,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=302;EINVAL:'), true);\n    });\n\n    test('transmit rejects t=f without id (no response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=t,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '');\n    });\n\n    test('transmit+display rejects t=f with id (EINVAL response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=310,a=T,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=310;EINVAL:'), true);\n    });\n\n    test('transmit+display rejects t=s with id (EINVAL response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=311,a=T,t=s,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=311;EINVAL:'), true);\n    });\n\n    test('transmit+display rejects t=t with id (EINVAL response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Gi=312,a=T,t=t,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response.startsWith('\\x1b_Gi=312;EINVAL:'), true);\n    });\n\n    test('transmit+display rejects t=f without id (no response)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=T,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '');\n    });\n  });\n\n  test.describe('Placement action (a=p)', () => {\n    test('displays a previously transmitted image at cursor', async () => {\n      // Transmit image without display\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=210;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 0);\n\n      // Place the previously transmitted image\n      await ctx.proxy.write(`\\x1b_Ga=p,i=210\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 255]);\n    });\n\n    test('responds OK on successful placement', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=211;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=211\\x1b\\\\`);\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=211;OK\\x1b\\\\');\n    });\n\n    test('responds ENOENT for non-existent image id', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=9999\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=9999;ENOENT:image not found\\x1b\\\\');\n    });\n\n    test('without id sends no response', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('places at specified column/row size (c/r)', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=212;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=212,c=5,r=3\\x1b\\\\`);\n      await timeout(200);\n\n      strictEqual(await getImageStorageLength(), 1);\n      deepStrictEqual(await getCursor(), [5, 2]);\n    });\n\n    test('cursor advances past placed image (default C=0)', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=213;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      deepStrictEqual(await getCursor(), [0, 0]);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=213\\x1b\\\\`);\n      await timeout(100);\n      deepStrictEqual(await getCursor(), [1, 0]);\n    });\n\n    test('cursor does not move when C=1', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=214;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      deepStrictEqual(await getCursor(), [0, 0]);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=214,c=5,r=3,C=1\\x1b\\\\`);\n      await timeout(200);\n      deepStrictEqual(await getCursor(), [0, 0]);\n    });\n\n    test('supports z-index (negative = bottom layer)', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=215;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=215,z=-1\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -1);\n    });\n\n    test('supports source crop via x/y', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=216;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      // Crop to the Orange rectangle (x=20, y=0, 20x50)\n      await ctx.proxy.write(`\\x1b_Ga=p,i=216,x=20,y=0,w=20,h=50\\x1b\\\\`);\n      await timeout(200);\n\n      strictEqual(await getImageStorageLength(), 1);\n      deepStrictEqual(await getOrigSize(1), [20, 50]);\n      deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 128, 0, 255]);\n    });\n\n    test('supports sub-cell offset via X/Y', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=217;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=217,X=5,Y=3\\x1b\\\\`);\n      await timeout(100);\n\n      deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 0]);\n      deepStrictEqual(await getPixel(0, 0, 4, 2), [0, 0, 0, 0]);\n      deepStrictEqual(await getPixel(0, 0, 5, 3), [0, 0, 0, 255]);\n    });\n\n    test('multiple placements of same image create separate displays', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=218;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 0);\n\n      // First placement\n      await ctx.proxy.write(`\\x1b_Ga=p,i=218,p=1\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n\n      // Move cursor down and place again with different placement ID\n      await ctx.proxy.write('\\x1b[3;1H');\n      await ctx.proxy.write(`\\x1b_Ga=p,i=218,p=2\\x1b\\\\`);\n      await timeout(100);\n\n      // Both placements should be in shared storage\n      strictEqual(await getImageStorageLength(), 2);\n    });\n\n    test('image data remains available after placement for future placements', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=219;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      // Place three times\n      await ctx.proxy.write(`\\x1b_Ga=p,i=219\\x1b\\\\`);\n      await timeout(100);\n      await ctx.proxy.write('\\x1b[2;1H');\n      await ctx.proxy.write(`\\x1b_Ga=p,i=219\\x1b\\\\`);\n      await timeout(100);\n      await ctx.proxy.write('\\x1b[3;1H');\n      await ctx.proxy.write(`\\x1b_Ga=p,i=219\\x1b\\\\`);\n      await timeout(100);\n\n      // Image data should still be available\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(219)`), true);\n    });\n\n    test('OK response suppressed by q=1', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=220;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=220,q=1\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n      strictEqual(await getImageStorageLength(), 1);\n    });\n\n    test('ENOENT error suppressed by q=2', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyGotResponse = false;\n        (window as any).term.onData(() => { (window as any).kittyGotResponse = true; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=9998,q=2\\x1b\\\\`);\n      await timeout(100);\n\n      strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);\n    });\n\n    test('ENOENT still reported when q=1 (only suppresses OK)', async () => {\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=9997,q=1\\x1b\\\\`);\n      await timeout(100);\n\n      const response: string = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=9997;ENOENT:image not found\\x1b\\\\');\n    });\n\n    test('renders pixels correctly when placing a PNG image', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=221;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=221\\x1b\\\\`);\n      await timeout(100);\n\n      const pixels = await getPixels(0, 0, 0, 0, 3, 1);\n      deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);\n      deepStrictEqual(pixels?.slice(4, 8), [0, 255, 0, 255]);\n      deepStrictEqual(pixels?.slice(8, 12), [0, 0, 255, 255]);\n    });\n\n    test('renders pixels correctly when placing raw RGB image', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=24,s=3,v=1,i=222;${RAW_RGB_3X1}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=222\\x1b\\\\`);\n      await timeout(100);\n\n      const pixels = await getPixels(0, 0, 0, 0, 3, 1);\n      deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);\n      deepStrictEqual(pixels?.slice(4, 8), [0, 255, 0, 255]);\n      deepStrictEqual(pixels?.slice(8, 12), [0, 0, 255, 255]);\n    });\n\n    test('renders pixels correctly when placing raw RGBA image', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=32,s=3,v=1,i=223;${RAW_RGBA_3X1}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=223\\x1b\\\\`);\n      await timeout(100);\n\n      const pixels = await getPixels(0, 0, 0, 0, 3, 1);\n      deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);\n      deepStrictEqual(pixels?.slice(4, 8), [0, 255, 0, 255]);\n      deepStrictEqual(pixels?.slice(8, 12), [0, 0, 255, 255]);\n    });\n\n    test('response includes placement id when p is specified', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=224;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.page.evaluate(() => {\n        (window as any).kittyResponse = '';\n        (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n      });\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=224,p=42\\x1b\\\\`);\n      await timeout(100);\n\n      const response = await ctx.page.evaluate('window.kittyResponse');\n      strictEqual(response, '\\x1b_Gi=224,p=42;OK\\x1b\\\\');\n    });\n\n    test('only c specified computes r from aspect ratio', async () => {\n      // 200x100 image (2:1 aspect) with c=10.\n      // Per spec: r = ceil((h/w) * c * cw / ch) = ceil(0.5 * 10 * cw / ch)\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=225;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=225,c=10\\x1b\\\\`);\n      await timeout(200);\n\n      const cursor = await getCursor();\n      const cellDims: number[] = await ctx.page.evaluate(() => {\n        const d = (window as any).term._core._renderService.dimensions.css.cell;\n        return [d.width, d.height];\n      });\n      const expectedR = Math.ceil((100 / 200) * 10 * cellDims[0] / cellDims[1]);\n      deepStrictEqual(cursor, [10, expectedR - 1]);\n    });\n\n    test('only r specified computes c from aspect ratio', async () => {\n      // 200x100 image (2:1 aspect) with r=5.\n      // Per spec: c = ceil((w/h) * r * ch / cw) = ceil(2 * 5 * ch / cw)\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=226;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      await ctx.proxy.write(`\\x1b_Ga=p,i=226,r=5\\x1b\\\\`);\n      await timeout(200);\n\n      const cursor = await getCursor();\n      const cellDims: number[] = await ctx.page.evaluate(() => {\n        const d = (window as any).term._core._renderService.dimensions.css.cell;\n        return [d.width, d.height];\n      });\n      const expectedC = Math.ceil((200 / 100) * 5 * cellDims[1] / cellDims[0]);\n      deepStrictEqual(cursor, [expectedC, 4]);\n    });\n  });\n\n  test.describe('Cursor positioning', () => {\n    // NOTE: Current tests document ACTUAL behavior (MVP - cursor doesn't move)\n    // Per Kitty spec: cursor placed at first column after last image column,\n    // on the last row of the image. C=1 means don't move cursor.\n\n    test('cursor advances past 1x1 image', async () => {\n      const cursorBefore = await getCursor();\n      const seq = `\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n      const cursorAfter = await getCursor();\n      deepStrictEqual(cursorBefore, [0, 0]);\n      // 1x1 pixel image occupies 1 column, cursor advances past it\n      deepStrictEqual(cursorAfter, [1, 0]);\n    });\n\n    test('cursor advances with text before image', async () => {\n      await ctx.proxy.write('Hello');\n      deepStrictEqual(await getCursor(), [5, 0]);\n\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      // Cursor advances 1 column past the image\n      deepStrictEqual(await getCursor(), [6, 0]);\n    });\n\n    test('cursor advances with text after image', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      // Cursor at column 1 (past 1-col image)\n      deepStrictEqual(await getCursor(), [1, 0]);\n\n      await ctx.proxy.write('World');\n      deepStrictEqual(await getCursor(), [6, 0]);\n    });\n\n    test('cursor position with multiple images on same line', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      deepStrictEqual(await getCursor(), [1, 0]);\n\n      await ctx.proxy.write('###');\n      deepStrictEqual(await getCursor(), [4, 0]);\n\n      // 3x1 pixel image: ceil(3/cellWidth)=1 column\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(50);\n      deepStrictEqual(await getCursor(), [5, 0]);\n    });\n\n    test('cursor advances on newline after image', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      deepStrictEqual(await getCursor(), [1, 0]);\n\n      await ctx.proxy.write('\\n');\n      deepStrictEqual(await getCursor(), [1, 1]);\n    });\n\n    test('cursor should move right by cols when c specified', async () => {\n      // c=5, r computed from 1x1 aspect ratio: r = ceil((1/1) * (5*cw) / ch)\n      // With 1:1 aspect, image height in pixels = 5*cw, so r = ceil(5*cw/ch)\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,c=5;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const cursor = await getCursor();\n      strictEqual(cursor[0], 5);\n      // r is computed from aspect ratio, so y > 0 for a square image at c=5\n    });\n\n    test('cursor should move down by rows when r specified', async () => {\n      // r=3, c computed from 1x1 aspect ratio: c = ceil((1/1) * (3*ch) / cw)\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,r=3;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const cursor = await getCursor();\n      strictEqual(cursor[1], 2);\n      // c is computed from aspect ratio, so x > 1 for a square image at r=3\n    });\n\n    test('cursor should move by cols AND rows when both specified', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,c=4,r=2;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      // cursor at (4, 1): past 4 columns, on last row (row 1)\n      deepStrictEqual(await getCursor(), [4, 1]);\n    });\n\n    test('cursor should NOT move when C=1 is specified', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,c=5,r=3,C=1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      // C=1: cursor stays at origin\n      deepStrictEqual(await getCursor(), [0, 0]);\n    });\n\n    test('cursor should calculate cols/rows from image size when not specified', async () => {\n      const dim = await getDimensions();\n\n      // 3x1 pixel image: cols = ceil(3/cellWidth), rows = ceil(1/cellHeight)\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n\n      const expectedCols = Math.ceil(3 / dim.cellWidth);\n      const cursor = await getCursor();\n\n      // Cursor advances past image columns, stays on row 0 (single row image)\n      strictEqual(cursor[0], expectedCols, 'cursor should advance by image columns');\n      strictEqual(cursor[1], 0, 'cursor should stay on row 0 for single-row image');\n    });\n  });\n\n  test.describe('Z-index layer placement', () => {\n    test('default placement (no z key) stores image on top layer', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), 0);\n    });\n\n    test('z=0 stores image on top layer', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=0;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), 0);\n    });\n\n    test('z=1 (positive) stores image on top layer', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), 1);\n    });\n\n    test('z=-1 uses bottom layer even when allowTransparency is disabled', async () => {\n      await ctx.page.evaluate(`window.term.options.allowTransparency = false`);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -1);\n    });\n\n    test('z=-1 (negative) stores image on bottom layer when allowTransparency is enabled', async () => {\n      await ctx.page.evaluate(`window.term.options.allowTransparency = true`);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -1);\n    });\n\n    test('z=-100 (large negative) stores image on bottom layer when allowTransparency is enabled', async () => {\n      await ctx.page.evaluate(`window.term.options.allowTransparency = true`);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=-100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -100);\n    });\n\n    test('top layer canvas has correct CSS class', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      const hasClass = await ctx.page.evaluate(() => {\n        const el = document.querySelector('.xterm-image-layer-top');\n        return el !== null;\n      });\n      strictEqual(hasClass, true);\n    });\n\n    test('bottom layer canvas has correct CSS class', async () => {\n      await ctx.page.evaluate(`window.term.options.allowTransparency = true`);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      const hasClass = await ctx.page.evaluate(() => {\n        const el = document.querySelector('.xterm-image-layer-bottom');\n        return el !== null;\n      });\n      strictEqual(hasClass, true);\n    });\n\n    test('bottom layer canvas is before text canvas in DOM order', async () => {\n      await ctx.page.evaluate(`window.term.options.allowTransparency = true`);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      const isFirst = await ctx.page.evaluate(() => {\n        const screen = document.querySelector('.xterm-screen');\n        return screen?.firstElementChild?.classList.contains('xterm-image-layer-bottom') ?? false;\n      });\n      strictEqual(isFirst, true);\n    });\n  });\n\n  test.describe('Pixel verification', () => {\n    test('renders 1x1 black PNG at cursor position', async () => {\n      const seq = `\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n\n      deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 255]);\n    });\n\n    test('renders 3x1 RGB PNG (red, green, blue pixels)', async () => {\n      const seq = `\\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`;\n      await ctx.proxy.write(seq);\n      await timeout(100);\n\n      const pixels = await getPixels(0, 0, 0, 0, 3, 1);\n\n      deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);\n      deepStrictEqual(pixels?.slice(4, 8), [0, 255, 0, 255]);\n      deepStrictEqual(pixels?.slice(8, 12), [0, 0, 255, 255]);\n    });\n  });\n\n  test.describe('Larger image (200x100 multicolor PNG)', () => {\n    test.describe('Basic transmission and storage', () => {\n      test('stores 200x100 PNG with a=T', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [200, 100]);\n      });\n\n      test('transmit only (a=t) stores 200x100 image without display', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=t,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.size`), 1);\n      });\n\n      test('stores with specified image ID', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=400;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(400)`), true);\n      });\n    });\n\n    test.describe('Chunked transmission', () => {\n      test('handles 2-chunk transmission', async () => {\n        const half = Math.floor(KITTY_MULTICOLOR_200X100_BASE64.length / 2);\n        const part1 = KITTY_MULTICOLOR_200X100_BASE64.substring(0, half);\n        const part2 = KITTY_MULTICOLOR_200X100_BASE64.substring(half);\n\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=500,m=1;${part1}\\x1b\\\\`);\n        await timeout(50);\n        strictEqual(await getImageStorageLength(), 0);\n\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=500;${part2}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [200, 100]);\n      });\n\n      test('handles 3-chunk transmission', async () => {\n        const third = Math.floor(KITTY_MULTICOLOR_200X100_BASE64.length / 3);\n        const p1 = KITTY_MULTICOLOR_200X100_BASE64.substring(0, third);\n        const p2 = KITTY_MULTICOLOR_200X100_BASE64.substring(third, third * 2);\n        const p3 = KITTY_MULTICOLOR_200X100_BASE64.substring(third * 2);\n\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=501,m=1;${p1}\\x1b\\\\`);\n        await timeout(50);\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=501,m=1;${p2}\\x1b\\\\`);\n        await timeout(50);\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=501;${p3}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [200, 100]);\n      });\n\n      test('verifies chunked data assembles correctly', async () => {\n        const half = Math.floor(KITTY_MULTICOLOR_200X100_BASE64.length / 2);\n        const part1 = KITTY_MULTICOLOR_200X100_BASE64.substring(0, half);\n        const part2 = KITTY_MULTICOLOR_200X100_BASE64.substring(half);\n\n        await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=502,m=1;${part1}\\x1b\\\\`);\n        await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=502;${part2}\\x1b\\\\`);\n        await timeout(200);\n\n        const storedData = await ctx.page.evaluate(async () => {\n          const blob = (window as any).imageAddon._handlers.get('kitty').images.get(502).data;\n          const buffer = await blob.arrayBuffer();\n          return Array.from(new Uint8Array(buffer));\n        });\n        deepStrictEqual(storedData, KITTY_MULTICOLOR_200X100_BYTES);\n      });\n    });\n\n    test.describe('Cursor positioning', () => {\n      test('cursor advances past multi-cell image', async () => {\n        const dim = await getDimensions();\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        const expectedCols = Math.ceil(200 / dim.cellWidth);\n        const expectedRows = Math.ceil(100 / dim.cellHeight) - 1;\n        const cursor = await getCursor();\n        strictEqual(cursor[0], expectedCols, 'cursor should advance by image columns');\n        strictEqual(cursor[1], expectedRows, 'cursor should be on last row of image');\n      });\n\n      test('cursor does not move with C=1', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,C=1;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        deepStrictEqual(await getCursor(), [0, 0]);\n      });\n\n      test('cursor uses explicit c and r over image dimensions', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,c=10,r=5;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        deepStrictEqual(await getCursor(), [10, 4]);\n      });\n    });\n\n    test.describe('Pixel verification', () => {\n      // The 200x100 image has 20 colored rectangles in a 10x2 grid.\n      // Each rectangle is 20px wide x 50px tall.\n      // Top row (y=0..49):  Red, Orange, Yellow, Lime, Green, Cyan, SkyBlue, Blue, Purple, Magenta\n      // Bottom row (y=50..99): Pink, Brown, Maroon, Olive, Teal, Navy, Gray, DarkGray, LightGray, White\n\n      test('renders red rectangle at top-left origin (0,0)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        // Pixel (0,0) is in the first rectangle: Red\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n      });\n\n      test('renders top row colors at rectangle centers', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        // Sample center of each top-row rectangle (y=25, x=10,30,50,...,190)\n        // All within the first cell row, so we read from the canvas at cell (0,0)\n        // Red at x=10\n        deepStrictEqual(await getPixel(0, 0, 10, 25), [255, 0, 0, 255]);\n        // Orange at x=30\n        deepStrictEqual(await getPixel(0, 0, 30, 25), [255, 128, 0, 255]);\n        // Yellow at x=50\n        deepStrictEqual(await getPixel(0, 0, 50, 25), [255, 255, 0, 255]);\n        // Lime at x=70\n        deepStrictEqual(await getPixel(0, 0, 70, 25), [0, 255, 0, 255]);\n        // Green at x=90\n        deepStrictEqual(await getPixel(0, 0, 90, 25), [0, 128, 0, 255]);\n      });\n\n      test('renders bottom row colors at rectangle centers', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        // Bottom row starts at y=50. Center at y=75.\n        // Pink at x=10\n        deepStrictEqual(await getPixel(0, 0, 10, 75), [255, 192, 203, 255]);\n        // Brown at x=30\n        deepStrictEqual(await getPixel(0, 0, 30, 75), [165, 42, 42, 255]);\n        // Maroon at x=50\n        deepStrictEqual(await getPixel(0, 0, 50, 75), [128, 0, 0, 255]);\n        // Olive at x=70\n        deepStrictEqual(await getPixel(0, 0, 70, 75), [128, 128, 0, 255]);\n        // Teal at x=90\n        deepStrictEqual(await getPixel(0, 0, 90, 75), [0, 128, 128, 255]);\n      });\n\n      test('renders correct colors at rectangle boundaries', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        // Last pixel of first rectangle (x=19, y=0): still Red\n        deepStrictEqual(await getPixel(0, 0, 19, 0), [255, 0, 0, 255]);\n        // First pixel of second rectangle (x=20, y=0): Orange\n        deepStrictEqual(await getPixel(0, 0, 20, 0), [255, 128, 0, 255]);\n        // Last pixel of top row (x=199, y=49): Magenta\n        deepStrictEqual(await getPixel(0, 0, 199, 49), [255, 0, 255, 255]);\n        // First pixel of bottom row (x=0, y=50): Pink\n        deepStrictEqual(await getPixel(0, 0, 0, 50), [255, 192, 203, 255]);\n      });\n\n      test('renders correct color at bottom-right corner', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        // Bottom-right corner (x=199, y=99): White\n        deepStrictEqual(await getPixel(0, 0, 199, 99), [255, 255, 255, 255]);\n      });\n\n      test('renders a strip of top-row pixels via getPixels', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        // Read 3 pixels starting at x=18 y=0, spanning the Red/Orange boundary\n        const pixels = await getPixels(0, 0, 18, 0, 3, 1);\n        // x=18,19 -> Red; x=20 -> Orange\n        deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);     // x=18: Red\n        deepStrictEqual(pixels?.slice(4, 8), [255, 0, 0, 255]);     // x=19: Red\n        deepStrictEqual(pixels?.slice(8, 12), [255, 128, 0, 255]);  // x=20: Orange\n      });\n\n      test('applies source crop via x/y/w/h before display', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,x=20,y=0,w=20,h=50;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        deepStrictEqual(await getOrigSize(1), [20, 50]);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 128, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 19, 49), [255, 128, 0, 255]);\n      });\n\n      test('scales cropped source region to c/r placement rectangle', async () => {\n        // Firefox's createImageBitmap uses different resize sampling, producing\n        // slightly off pixel values compared to Chromium, so skip on Firefox.\n        if (ctx.browser.browserType().name() === 'firefox') {\n          test.skip();\n        }\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,x=1,y=0,w=1,h=1,c=4,r=2;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        deepStrictEqual(await getCursor(), [4, 1]);\n        const left = await getPixel(0, 0, 2, 10);\n        const right = await getPixel(0, 0, 25, 10);\n        deepStrictEqual(left, [0, 255, 0, 255]);\n        deepStrictEqual(right, [0, 255, 0, 255]);\n      });\n\n      test('applies sub-cell offset via X/Y within first cell', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,X=5,Y=3;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n        await timeout(100);\n\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 0]);\n        deepStrictEqual(await getPixel(0, 0, 4, 2), [0, 0, 0, 0]);\n        deepStrictEqual(await getPixel(0, 0, 5, 3), [0, 0, 0, 255]);\n      });\n\n      test('w=0 is treated as unset (displays full width)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,w=0;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        deepStrictEqual(await getOrigSize(1), [200, 100]);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 199, 99), [255, 255, 255, 255]);\n      });\n\n      test('h=0 is treated as unset (displays full height)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,h=0;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        deepStrictEqual(await getOrigSize(1), [200, 100]);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 0, 50), [255, 192, 203, 255]);\n      });\n\n      test('x exceeding image width produces no display', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,x=999;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('negative x/y values are clamped to 0', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,x=-10,y=-10;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        deepStrictEqual(await getOrigSize(1), [200, 100]);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n      });\n\n      test('combined crop and sub-cell offset', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,x=20,y=0,w=20,h=50,X=5,Y=3;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 0]);\n        deepStrictEqual(await getPixel(0, 0, 4, 2), [0, 0, 0, 0]);\n        deepStrictEqual(await getPixel(0, 0, 5, 3), [255, 128, 0, 255]);\n      });\n\n      test('sub-cell offset with explicit c/r advances cursor correctly', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=100,X=5,Y=3,c=4,r=2;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n        await timeout(100);\n\n        deepStrictEqual(await getCursor(), [4, 1]);\n      });\n    });\n\n    test.describe('Query support', () => {\n      test('responds with OK for valid 200x100 PNG query', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n\n        await ctx.proxy.write(`\\x1b_Gi=600,a=q,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=600;OK\\x1b\\\\');\n      });\n\n      test('query does not store the 200x100 image', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).term.onData(() => { /* consume response */ });\n        });\n\n        await ctx.proxy.write(`\\x1b_Gi=601,a=q,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(601)`), false);\n      });\n    });\n\n    test.describe('Delete commands', () => {\n      test('delete removes 200x100 image by id', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=700;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n        await timeout(200);\n        strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(700)`), true);\n\n        await ctx.proxy.write(`\\x1b_Ga=d,d=i,i=700\\x1b\\\\`);\n        await timeout(50);\n        strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(700)`), false);\n      });\n    });\n  });\n\n  test.describe('Raw RGB pixel format (f=24)', () => {\n    test.describe('Pixel verification', () => {\n      test('renders 1x1 black pixel with alpha set to 255', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=1,v=1;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [0, 0, 0, 255]);\n      });\n\n      test('renders 1x1 red pixel with alpha set to 255', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=1,v=1;${RAW_RGB_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n      });\n\n      test('renders 3x1 strip (red, green, blue)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=3,v=1;${RAW_RGB_3X1}\\x1b\\\\`);\n        await timeout(100);\n\n        const pixels = await getPixels(0, 0, 0, 0, 3, 1);\n        deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);\n        deepStrictEqual(pixels?.slice(4, 8), [0, 255, 0, 255]);\n        deepStrictEqual(pixels?.slice(8, 12), [0, 0, 255, 255]);\n      });\n\n      test('renders 2x2 grid with correct pixel layout', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=2,v=2;${RAW_RGB_2X2}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 0), [0, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 0, 1), [0, 0, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 1), [255, 255, 0, 255]);\n      });\n\n      test('renders 5x1 row with block+remainder pixel layout', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=5,v=1;${RAW_RGB_5X1}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 0), [0, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 2, 0), [0, 0, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 3, 0), [255, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 4, 0), [255, 0, 255, 255]);\n      });\n\n      test('renders 4x2 grid with multi-block pixel layout', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=4,v=2;${RAW_RGB_4X2}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 0), [0, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 2, 0), [0, 0, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 3, 0), [255, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 0, 1), [255, 0, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 1), [0, 255, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 2, 1), [128, 128, 128, 255]);\n        deepStrictEqual(await getPixel(0, 0, 3, 1), [255, 255, 255, 255]);\n      });\n    });\n\n    test.describe('Storage and dimensions', () => {\n      test('stores image with correct original dimensions (3x1)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=3,v=1;${RAW_RGB_3X1}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [3, 1]);\n      });\n\n      test('stores image with correct original dimensions (2x2)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=2,v=2;${RAW_RGB_2X2}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [2, 2]);\n      });\n\n      test('stores image with correct original dimensions (5x1)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=5,v=1;${RAW_RGB_5X1}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [5, 1]);\n      });\n\n      test('stores image with correct original dimensions (4x2)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=4,v=2;${RAW_RGB_4X2}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [4, 2]);\n      });\n    });\n\n    test.describe('Validation', () => {\n      test('does not render without width (s=)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,v=1;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('does not render without height (v=)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=1;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('does not render without either dimension', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('does not render with insufficient byte count', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=24,s=2,v=2;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('query returns EINVAL without dimensions', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n        await ctx.proxy.write(`\\x1b_Gi=200,a=q,f=24;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=200;EINVAL:width and height required for raw pixel data\\x1b\\\\');\n      });\n\n      test('query returns EINVAL for insufficient pixel data', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n        await ctx.proxy.write(`\\x1b_Gi=201,a=q,f=24,s=2,v=2;${RAW_RGB_1X1_BLACK}\\x1b\\\\`);\n        await timeout(100);\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=201;EINVAL:insufficient pixel data\\x1b\\\\');\n      });\n\n      test('query returns OK for valid RGB data with correct dimensions', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n        await ctx.proxy.write(`\\x1b_Gi=202,a=q,f=24,s=1,v=1;${RAW_RGB_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=202;OK\\x1b\\\\');\n      });\n    });\n  });\n\n  test.describe('Raw RGBA pixel format (f=32)', () => {\n    test.describe('Pixel verification', () => {\n      test('renders 1x1 opaque white pixel', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=1,v=1;${RAW_RGBA_1X1_WHITE}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 255, 255, 255]);\n      });\n\n      test('renders 1x1 opaque red pixel', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=1,v=1;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n      });\n\n      test('preserves full transparency (alpha=0)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=1,v=1;${RAW_RGBA_1X1_TRANSPARENT}\\x1b\\\\`);\n        await timeout(100);\n        const pixel = await getPixel(0, 0, 0, 0);\n        strictEqual(pixel?.[3], 0);\n      });\n\n      test('renders 3x1 strip (red, green, blue opaque)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=3,v=1;${RAW_RGBA_3X1}\\x1b\\\\`);\n        await timeout(100);\n\n        const pixels = await getPixels(0, 0, 0, 0, 3, 1);\n        deepStrictEqual(pixels?.slice(0, 4), [255, 0, 0, 255]);\n        deepStrictEqual(pixels?.slice(4, 8), [0, 255, 0, 255]);\n        deepStrictEqual(pixels?.slice(8, 12), [0, 0, 255, 255]);\n      });\n\n      test('renders 2x2 grid with correct pixel layout', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=2,v=2;${RAW_RGBA_2X2}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 0), [0, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 0, 1), [0, 0, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 1), [255, 255, 0, 255]);\n      });\n\n      test('renders 5x1 row with zero-copy pixel layout', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=5,v=1;${RAW_RGBA_5X1}\\x1b\\\\`);\n        await timeout(100);\n        deepStrictEqual(await getPixel(0, 0, 0, 0), [255, 0, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 1, 0), [0, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 2, 0), [0, 0, 255, 255]);\n        deepStrictEqual(await getPixel(0, 0, 3, 0), [255, 255, 0, 255]);\n        deepStrictEqual(await getPixel(0, 0, 4, 0), [255, 0, 255, 255]);\n      });\n    });\n\n    test.describe('Storage and dimensions', () => {\n      test('stores image with correct original dimensions (3x1)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=3,v=1;${RAW_RGBA_3X1}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [3, 1]);\n      });\n\n      test('stores image with correct original dimensions (2x2)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=2,v=2;${RAW_RGBA_2X2}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [2, 2]);\n      });\n\n      test('stores image with correct original dimensions (5x1)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=5,v=1;${RAW_RGBA_5X1}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 1);\n        deepStrictEqual(await getOrigSize(1), [5, 1]);\n      });\n    });\n\n    test.describe('Validation', () => {\n      test('does not render without width (s=)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,v=1;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('does not render without height (v=)', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=1;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('does not render without either dimension', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('does not render with insufficient byte count', async () => {\n        await ctx.proxy.write(`\\x1b_Ga=T,f=32,s=2,v=2;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        strictEqual(await getImageStorageLength(), 0);\n      });\n\n      test('query returns EINVAL without dimensions', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n        await ctx.proxy.write(`\\x1b_Gi=300,a=q,f=32;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=300;EINVAL:width and height required for raw pixel data\\x1b\\\\');\n      });\n\n      test('query returns EINVAL for insufficient pixel data', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n        await ctx.proxy.write(`\\x1b_Gi=301,a=q,f=32,s=2,v=2;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=301;EINVAL:insufficient pixel data\\x1b\\\\');\n      });\n\n      test('query returns OK for valid RGBA data with correct dimensions', async () => {\n        await ctx.page.evaluate(() => {\n          (window as any).kittyResponse = '';\n          (window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });\n        });\n        await ctx.proxy.write(`\\x1b_Gi=302,a=q,f=32,s=1,v=1;${RAW_RGBA_1X1_RED}\\x1b\\\\`);\n        await timeout(100);\n        const response = await ctx.page.evaluate('window.kittyResponse');\n        strictEqual(response, '\\x1b_Gi=302;OK\\x1b\\\\');\n      });\n    });\n  });\n\n  test.describe('Eviction and memory leak prevention', () => {\n    test('re-transmit with same i= cleans up old storage entry', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=50;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(50)`), true);\n      const oldStorageId = await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.get(50)`);\n      ok(oldStorageId !== undefined);\n\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=50;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(50)`), true);\n      const newStorageId = await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.get(50)`);\n      ok(newStorageId !== undefined);\n      ok(newStorageId !== oldStorageId);\n    });\n\n    test('memory limit eviction cleans Kitty handler maps', async () => {\n      // Resize terminal to fit 7 non-overlapping 200x100 images without scrolling.\n      // Each image ≈ 29 cols × 8 rows at default cell size.\n      await ctx.page.evaluate(`\n        window.term.reset();\n        window.imageAddon?.dispose();\n        window.term.resize(80, 48);\n        window.imageAddon = new ImageAddon({ storageLimit: 0.5 });\n        window.term.loadAddon(window.imageAddon);\n      `);\n\n      // storageLimit 0.5 MB = 125,000 pixels. Each 200x100 image = 20,000 pixels.\n      // 6 images = 120K pixels (under limit). 7th triggers eviction (140K > 125K).\n      // Place non-overlapping so tile-count eviction doesn't interfere.\n      const positions = [[1, 1], [30, 1], [1, 9], [30, 9], [1, 17], [30, 17]];\n      for (let n = 0; n < 6; n++) {\n        const [c, r] = positions[n];\n        const id = 60 + n;\n        await ctx.proxy.write(`\\x1b[${r};${c}H\\x1b_Ga=T,f=100,i=${id},C=1;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      }\n      await pollFor(ctx.page, 'window.imageAddon._storage._images.size', 6);\n\n      // 7th image pushes total past 125K pixels — oldest evicted\n      await ctx.proxy.write(`\\x1b[25;1H\\x1b_Ga=T,f=100,i=66,C=1;${KITTY_MULTICOLOR_200X100_BASE64}\\x1b\\\\`);\n      await pollFor(ctx.page, `window.imageAddon._handlers.get('kitty').images.has(60)`, false);\n\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.has(60)`), false);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(66)`), true);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.has(66)`), true);\n\n      // Restore terminal size\n      await ctx.page.evaluate('window.term.resize(80, 24)');\n    });\n\n    test('scrollback eviction cleans Kitty handler maps', async () => {\n      await ctx.page.evaluate(`\n        window.term.reset();\n        window.imageAddon?.dispose();\n        window.imageAddon = new ImageAddon();\n        window.term.loadAddon(window.imageAddon);\n      `);\n\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=70;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty').images.has(70)`), true);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.has(70)`), true);\n\n      // Scroll past scrollback + viewport to push image's marker off the buffer\n      await ctx.page.evaluate(() => new Promise<void>(res => {\n        const term = (window as any).term;\n        const amount: number = (term.options.scrollback as number) + (term.rows as number) + 10;\n        term.write('\\n'.repeat(amount), res);\n      }));\n\n      await pollFor(ctx.page, `window.imageAddon._handlers.get('kitty').images.has(70)`, false);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.has(70)`), false);\n    });\n\n    test('re-transmit with a=t then a=T cleans old storage before display', async () => {\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100,i=80;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await getImageStorageLength(), 1);\n      const oldStorageId = await ctx.page.evaluate(`window.imageAddon._handlers.get('kitty')._kittyIdToStorageId.get(80)`);\n      ok(oldStorageId !== undefined);\n\n      await ctx.proxy.write(`\\x1b_Ga=t,f=100,i=80;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await timeout(100);\n      strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.has(${oldStorageId})`), false);\n    });\n  });\n\n  test.describe('onImageAdded callback', () => {\n    test('onImageAdded fires for each kitty image', async () => {\n      await ctx.page.evaluate(`\n        window._imageAddedCount = 0;\n        window.imageAddon.onImageAdded(() => { window._imageAddedCount++; });\n      `);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\\x1b\\\\`);\n      await pollFor(ctx.page, 'window._imageAddedCount', 1);\n      await ctx.proxy.write(`\\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\\x1b\\\\`);\n      await pollFor(ctx.page, 'window._imageAddedCount', 2);\n    });\n  });\n});\n\n/**\n * Helper functions\n */\nasync function getDimensions(): Promise<IDimensions> {\n  const dimensions: any = await ctx.page.evaluate(`term.dimensions`);\n  return {\n    cellWidth: Math.round(dimensions.css.cell.width),\n    cellHeight: Math.round(dimensions.css.cell.height),\n    width: Math.round(dimensions.css.canvas.width),\n    height: Math.round(dimensions.css.canvas.height)\n  };\n}\n\nasync function getCursor(): Promise<[number, number]> {\n  return ctx.page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]');\n}\n\nasync function getImageStorageLength(): Promise<number> {\n  return ctx.page.evaluate('window.imageAddon._storage._images.size');\n}\n\nasync function getOrigSize(id: number): Promise<[number, number]> {\n  return ctx.page.evaluate<any>(`[\n    window.imageAddon._storage._images.get(${id}).orig.width,\n    window.imageAddon._storage._images.get(${id}).orig.height\n  ]`);\n}\n\nasync function getPixel(col: number, row: number, x: number, y: number): Promise<number[] | null> {\n  return ctx.page.evaluate(([col, row, x, y]: number[]) => {\n    const canvas = (window as any).imageAddon.getImageAtBufferCell(col, row);\n    if (!canvas) return null;\n    const ctx2d = canvas.getContext('2d');\n    if (!ctx2d) return null;\n    return Array.from(ctx2d.getImageData(x, y, 1, 1).data);\n  }, [col, row, x, y]);\n}\n\nasync function getPixels(col: number, row: number, x: number, y: number, w: number, h: number): Promise<number[] | null> {\n  return ctx.page.evaluate(([col, row, x, y, w, h]: number[]) => {\n    const canvas = (window as any).imageAddon.getImageAtBufferCell(col, row);\n    if (!canvas) return null;\n    const ctx2d = canvas.getContext('2d');\n    if (!ctx2d) return null;\n    return Array.from(ctx2d.getImageData(x, y, w, h).data);\n  }, [col, row, x, y, w, h]);\n}\n"
  },
  {
    "path": "addons/addon-image/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-image/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-image/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-image/typings/addon-image.d.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-image' {\n  export interface IImageAddonOptions {\n    /**\n     * Enable size reports in windowOptions:\n     * - getWinSizePixels (CSI 14 t)\n     * - getCellSizePixels (CSI 16 t)\n     * - getWinSizeChars (CSI 18 t)\n     *\n     * If `true` (default), the reports will be activated during addon loading.\n     * If `false`, no settings will be touched. Use `false`, if you have high\n     * security constraints and/or deal with windowOptions by other means.\n     * On addon disposal, the settings will not change.\n     */\n    enableSizeReports?: boolean;\n\n    /**\n     * Maximum pixels a single image may hold. Images exceeding this number will\n     * be discarded during processing with no changes to the terminal buffer\n     * (no cursor advance, no placeholder).\n     * This setting is mainly used to restrict images sizes during initial decoding\n     * including the final canvas creation.\n     *\n     * Note: The image worker decoder may hold additional memory up to\n     * `pixelLimit` * 4 bytes permanently, plus the same amount on top temporarily\n     * for pixel transfers, which should be taken into account under memory pressure conditions.\n     *\n     * Note: Browsers restrict allowed canvas dimensions further. We dont reflect those\n     * limits here, thus the construction of an oddly shaped image having most pixels\n     * in one dimension still can fail.\n     *\n     * Note: `storageLimit` bytes are calculated from images by multiplying the pixels with 4\n     * (4 channels with one byte, images are stored as RGBA8888).\n     *\n     * Default is 2^16 (4096 x 4096 pixels).\n     */\n    pixelLimit?: number;\n\n    /**\n     * Storage limit in MB.\n     * The storage implements a FIFO cache removing old images, when the limit gets hit.\n     * Also exposed as addon property for runtime adjustments.\n     * Default is 128 MB.\n     */\n    storageLimit?: number;\n\n    /**\n     * Whether to show a placeholder for images removed from cache, default is true.\n     */\n    showPlaceholder?: boolean;\n\n    /**\n     * SIXEL settings\n     */\n\n    /** Whether SIXEL is enabled (default is true). */\n    sixelSupport?: boolean;\n    /** Whether SIXEL scrolling is enabled (default is true). Same as DECSET 80. */\n    sixelScrolling?: boolean;\n    /** Palette color limit (default 256). */\n    sixelPaletteLimit?: number;\n    /** SIXEL image size limit in bytes (default 25000000 bytes). */\n    sixelSizeLimit?: number;\n\n    /**\n     * IIP settings (iTerm image protocol)\n     */\n\n    /** Whether iTerm image protocol style is enabled (default is true). */\n    iipSupport?: boolean;\n    /** IIP sequence size limit (default 20000000 bytes). */\n    iipSizeLimit?: number;\n\n    /**\n     * Kitty graphics protocol settings\n     */\n\n    /** Whether Kitty graphics protocol is enabled (default is true). */\n    kittySupport?: boolean;\n    /** Kitty image size limit in bytes (default 20000000 bytes). */\n    kittySizeLimit?: number;\n  }\n\n  export class ImageAddon implements ITerminalAddon {\n    constructor(options?: IImageAddonOptions);\n    public activate(terminal: Terminal): void;\n    public dispose(): void;\n\n    /**\n     * Reset the image addon.\n     *\n     * This resets all runtime options to default values (as given to the ctor)\n     * and resets the image storage.\n     */\n    public reset(): void;\n\n    /**\n     * Getter/Setter for the storageLimit in MB.\n     * Synchronously deletes images if the stored data exceeds the new value.\n     */\n    public storageLimit: number;\n\n    /**\n     * Current memory usage of the stored images in MB.\n     */\n    public readonly storageUsage: number;\n\n    /**\n     * Getter/Setter whether the placeholder should be shown.\n     */\n    public showPlaceholder: boolean;\n\n    /**\n     * Event fired whenever a new image is added to storage.\n     */\n    public readonly onImageAdded: IEvent<void>;\n\n    /**\n     * Get original image canvas at buffer position.\n     */\n    public getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined;\n\n    /**\n     * Extract single tile canvas at buffer position.\n     */\n    public extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined;\n  }\n}\n"
  },
  {
    "path": "addons/addon-image/webpack.config.js",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'ImageAddon';\nconst mainFile = 'addon-image.js';\n\nconst addon = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      browser: path.resolve('../../out/browser'),\n      vs: path.resolve('../../out/vs')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n\nmodule.exports = [addon];\n"
  },
  {
    "path": "addons/addon-ligatures/.gitignore",
    "content": "node_modules/\n.nyc_output/\ncoverage/\n\nlib/\n\n.env\n.vscode/\n*.swp\n*.tgz\nnpm-debug.log*\n"
  },
  {
    "path": "addons/addon-ligatures/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-ligatures/LICENSE",
    "content": "Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\nThe code that analyzes font ligatures is forked from https://github.com/princjef/font-ligatures with this license:\n\nMIT License\n\nCopyright (c) 2018 Jeffrey Principe\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "addons/addon-ligatures/README.md",
    "content": "## @xterm/addon-ligatures\n\nAdd support for programming ligatures to [xterm.js] when running in environments with access to [Node.js] APIs (such as [Electron]).\n\n### Requirements\n\n * [Node.js] 8.x or higher (present in [Electron] 1.8.3 or higher)\n * [xterm.js] 4.0.0 or higher using the default canvas renderer\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-ligatures\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { LigaturesAddon } from '@xterm/addon-ligatures';\n\nconst terminal = new Terminal();\nconst ligaturesAddon = new LigaturesAddon();\nterminal.open(containerElement);\nterminal.loadAddon(ligaturesAddon);\n```\n\n### How It Works\n\nIn a browser environment, font ligature information is read directly by the web browser and used to render text correctly without any intervention from the developer. As of version 3, xterm.js uses the canvas to render characters individually, resulting in a significant performance boost. However, this means that it can no longer lean on the browser to determine when to draw font ligatures.\n\nThis package locates the font file on disk for the font currently in use by the terminal and parses the ligature information out of it (via the [font-ligatures] package). As text is rendered in xterm.js, this package annotates it with the locations of ligatures, allowing xterm.js to render it correctly.\n\nSince this package depends on being able to find and resolve a system font from disk, it has to have system access that isn't available in the web browser. As a result, this package is mainly useful in environments that combine browser and Node.js runtimes (such as [Electron]).\n\n### Fallback Ligatures\n\nWhen ligatures cannot be fetched from the environment, a set of \"fallback\" ligatures is used to get the most common ligatures working. These fallback ligatures can be customized with options passed to `LigatureAddon.constructor`.\n\n### Fonts\n\nThis package makes use of the following fonts for testing:\n\n* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and Fira Sans\n* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with reserved name Iosevka\n\n[xterm.js]: https://github.com/xtermjs/xterm.js\n[Electron]: https://electronjs.org/\n[Node.js]: https://nodejs.org/\n[font-ligatures]: https://github.com/princjef/font-ligatures\n[Fira Code]: https://github.com/tonsky/FiraCode\n[Fira Code License]: https://github.com/tonsky/FiraCode/blob/master/LICENSE\n[Iosevka]: https://github.com/be5invis/Iosevka\n[Iosevka License]: https://github.com/be5invis/Iosevka/blob/master/LICENSE.md\n\n"
  },
  {
    "path": "addons/addon-ligatures/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-ligatures\",\n  \"version\": \"0.10.0\",\n  \"description\": \"Add support for programming ligatures to xterm.js\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-ligatures.js\",\n  \"module\": \"lib/addon-ligatures.mjs\",\n  \"types\": \"typings/addon-ligatures.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-ligatures\",\n  \"engines\": {\n    \"node\": \">8.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"tsgo -p src\",\n    \"watch\": \"tsgo -w -p src\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"webpack\",\n    \"pretest\": \"npm run build\",\n    \"test\": \"nyc mocha out/**/*.test.js\",\n    \"prepublish\": \"npm run package\"\n  },\n  \"keywords\": [\n    \"font\",\n    \"ligature\",\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"lru-cache\": \"^6.0.0\",\n    \"opentype.js\": \"^0.8.0\"\n  },\n  \"devDependencies\": {\n    \"@types/lru-cache\": \"^5.1.0\",\n    \"@types/opentype.js\": \"^0.7.0\",\n    \"axios\": \"^1.6.0\",\n    \"font-finder\": \"^1.1.0\",\n    \"mkdirp\": \"0.5.5\",\n    \"yauzl\": \"^3.2.1\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/LigaturesAddon.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal } from '@xterm/xterm';\nimport type { LigaturesAddon as ILigaturesApi } from '@xterm/addon-ligatures';\nimport { enableLigatures } from '.';\nimport { ILigatureOptions } from './Types';\n\nexport interface ITerminalAddon {\n  activate(terminal: Terminal): void;\n  dispose(): void;\n}\n\nexport class LigaturesAddon implements ITerminalAddon, ILigaturesApi {\n  private readonly _fallbackLigatures: string[];\n  private readonly _fontFeatureSettings?: string;\n\n  private _terminal: Terminal | undefined;\n  private _characterJoinerId: number | undefined;\n\n  constructor(options?: Partial<ILigatureOptions>) {\n    // Source: calt set from https://github.com/be5invis/Iosevka?tab=readme-ov-file#ligations\n    this._fallbackLigatures = (options?.fallbackLigatures ?? [\n      '<--', '<---', '<<-', '<-', '->', '->>', '-->', '--->',\n      '<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=',\n      '<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '::', ':::',\n      '<~~', '</', '</>', '/>', '~~>', '==', '!=', '/=', '~=', '<>', '===', '!==', '!===',\n      '<:', ':=', '*=', '*+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+*', '=*', '=:', ':>',\n      '/*', '*/', '+++', '<!--', '<!---'\n    ]).sort((a, b) => b.length - a.length);\n    this._fontFeatureSettings = options?.fontFeatureSettings;\n  }\n\n  public activate(terminal: Terminal): void {\n    if (!terminal.element) {\n      throw new Error('Cannot activate LigaturesAddon before open is called');\n    }\n    this._terminal = terminal;\n    this._characterJoinerId = enableLigatures(terminal, this._fallbackLigatures);\n    terminal.element.style.fontFeatureSettings = this._fontFeatureSettings ?? '\"calt\" on';\n  }\n\n  public dispose(): void {\n    if (this._characterJoinerId !== undefined) {\n      this._terminal?.deregisterCharacterJoiner(this._characterJoinerId);\n      this._characterJoinerId = undefined;\n    }\n    if (this._terminal?.element) {\n      this._terminal.element.style.fontFeatureSettings = '';\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/Types.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport interface ILigatureOptions {\n  fallbackLigatures: string[];\n  fontFeatureSettings: string;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/font.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Font, loadBuffer } from './fontLigatures/index';\n\nimport parse from './parse';\n\ninterface IFontMetadata {\n  family: string;\n  fullName: string;\n  postscriptName: string;\n  blob: () => Promise<Blob>;\n}\n\ninterface IFontAccessNavigator {\n  fonts: {\n    query: () => Promise<IFontMetadata[]>;\n  };\n  permissions: {\n    request?: (permission: { name: string }) => Promise<{state: string}>;\n  };\n}\n\nlet fontsPromise: Promise<Record<string, IFontMetadata[]>> | undefined = undefined;\n\n/**\n * Loads the font ligature wrapper for the specified font family if it could be\n * resolved, throwing if it is unable to find a suitable match.\n * @param fontFamily The CSS font family definition to resolve\n * @param cacheSize The size of the ligature cache to maintain if the font is resolved\n */\nexport default async function load(fontFamily: string, cacheSize: number): Promise<Font | undefined> {\n  if (!fontsPromise) {\n    // Web environment that supports font access API\n    if (typeof navigator !== 'undefined' && 'fonts' in navigator) {\n      try {\n        const status = await (navigator as unknown as IFontAccessNavigator).permissions.request?.({\n          name: 'local-fonts'\n        });\n        if (status && status.state !== 'granted') {\n          throw new Error('Permission to access local fonts not granted.');\n        }\n      } catch (err: unknown) {\n        // A `TypeError` indicates the 'local-fonts'\n        // permission is not yet implemented, so\n        // only `throw` if this is _not_ the problem.\n        if (err instanceof Error && err.name !== 'TypeError') {\n          throw err;\n        }\n      }\n      const fonts: Record<string, IFontMetadata[]> = {};\n      try {\n        const fontsIterator = await (navigator as unknown as IFontAccessNavigator).fonts.query();\n        for (const metadata of fontsIterator) {\n          if (!fonts.hasOwnProperty(metadata.family)) {\n            fonts[metadata.family] = [];\n          }\n          fonts[metadata.family].push(metadata);\n        }\n        fontsPromise = Promise.resolve(fonts);\n      } catch (err: unknown) {\n        if (err instanceof Error) {\n          console.error(err.name, err.message);\n        }\n      }\n    }\n    // Latest proposal https://bugs.chromium.org/p/chromium/issues/detail?id=1312603\n    else if (typeof window !== 'undefined' && 'queryLocalFonts' in window) {\n      const fonts: Record<string, IFontMetadata[]> = {};\n      try {\n        const fontsIterator = await (window as any).queryLocalFonts();\n        for (const metadata of fontsIterator) {\n          if (!fonts.hasOwnProperty(metadata.family)) {\n            fonts[metadata.family] = [];\n          }\n          fonts[metadata.family].push(metadata);\n        }\n        fontsPromise = Promise.resolve(fonts);\n      } catch (err: unknown) {\n        if (err instanceof Error) {\n          console.error(err.name, err.message);\n        }\n      }\n    }\n    fontsPromise ??= Promise.resolve({});\n  }\n\n  const fonts = await fontsPromise;\n  for (const family of parse(fontFamily)) {\n    // If we reach one of the generic font families, the font resolution\n    // will end for the browser and we can't determine the specific font\n    // used. Throw.\n    if (genericFontFamilies.includes(family)) {\n      return undefined;\n    }\n\n    if (fonts.hasOwnProperty(family) && fonts[family].length > 0) {\n      const font = fonts[family][0];\n      if ('blob' in font) {\n        const bytes = await font.blob();\n        const buffer = await bytes.arrayBuffer();\n        return loadBuffer(buffer, { cacheSize });\n      }\n      return undefined;\n    }\n  }\n\n  // If none of the fonts could resolve, throw an error\n  return undefined;\n}\n\n// https://drafts.csswg.org/css-fonts-4/#generic-font-families\nconst genericFontFamilies = [\n  'serif',\n  'sans-serif',\n  'cursive',\n  'fantasy',\n  'monospace',\n  'system-ui',\n  'emoji',\n  'math',\n  'fangsong'\n];\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/flatten.ts",
    "content": "import { ILookupTree, IFlattenedLookupTree, ILookupTreeEntry, IFlattenedLookupTreeEntry } from './types';\n\nexport default function flatten(tree: ILookupTree, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry> = new Map()): IFlattenedLookupTree {\n  const result: IFlattenedLookupTree = {};\n  for (const [glyphId, entry] of Object.entries(tree.individual)) {\n    result[glyphId] = flattenEntry(entry, visited);\n  }\n\n  for (const { range, entry } of tree.range) {\n    const flattened = flattenEntry(entry, visited);\n    for (let glyphId = range[0]; glyphId < range[1]; glyphId++) {\n      result[glyphId] = flattened;\n    }\n  }\n\n  return result;\n}\n\nfunction flattenEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry>): IFlattenedLookupTreeEntry {\n  if (visited.has(entry)) {\n    return visited.get(entry)!;\n  }\n\n  const result: IFlattenedLookupTreeEntry = {};\n  visited.set(entry, result);\n\n  if (entry.forward) {\n    result.forward = flatten(entry.forward, visited);\n  }\n\n  if (entry.reverse) {\n    result.reverse = flatten(entry.reverse, visited);\n  }\n\n  if (entry.lookup) {\n    result.lookup = entry.lookup;\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/index.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { assert } from 'chai';\nimport { loadBuffer } from './index';\n\ninterface IFont {\n  findLigatures(text: string): { outputGlyphs: number[], contextRanges: [number, number][] };\n  findLigatureRanges(text: string): [number, number][];\n}\n\ninterface ITestCase {\n  font: string;\n  input: string;\n  glyphs: number[];\n  ranges: [number, number][];\n}\n\nconst fira = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>\n  ({ font: 'Fira Code', input, glyphs, ranges });\n\nconst iosevka = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>\n  ({ font: 'Iosevka', input, glyphs, ranges });\n\nconst monoid = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>\n  ({ font: 'Monoid', input, glyphs, ranges });\n\nconst ubuntu = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>\n  ({ font: 'Ubuntu Mono', input, glyphs, ranges });\n\nconst firaCases: ITestCase[] = [\n  fira('abc', [133, 145, 146], []),\n  fira('.=', [1614, 1081], [[0, 2]]),\n  fira('..=', [1614, 1614, 1083], [[0, 3]]),\n  fira('.-', [1614, 1080], [[0, 2]]),\n  fira(':=', [1614, 1055], [[0, 2]]),\n  fira('=:=', [1614, 1614, 1483], [[0, 3]]),\n  fira('=!=', [1614, 1614, 1484], [[0, 3]]),\n  fira('__', [1614, 1099], [[0, 2]]),\n  fira('==', [1614, 1485], [[0, 2]]),\n  fira('!=', [1614, 1058], [[0, 2]]),\n  fira('===', [1614, 1614, 1486], [[0, 3]]),\n  fira('!==', [1614, 1614, 1059], [[0, 3]]),\n  fira('=/=', [1614, 1614, 1491], [[0, 3]]),\n  fira('<-<', [1614, 1614, 1513], [[0, 3]]),\n  fira('<<-', [1614, 1614, 1522], [[0, 3]]),\n  fira('<--', [1614, 1614, 1511], [[0, 3]]),\n  fira('<-', [1614, 1510], [[0, 2]]),\n  fira('<->', [1614, 1614, 1512], [[0, 3]]),\n  fira('->', [1614, 1064], [[0, 2]]),\n  fira('-->', [1614, 1614, 1063], [[0, 3]]),\n  fira('->>', [1614, 1614, 1065], [[0, 3]]),\n  fira('>->', [1614, 1614, 1493], [[0, 3]]),\n  fira('<=<', [1614, 1614, 1519], [[0, 3]]),\n  fira('<<=', [1614, 1614, 1523], [[0, 3]]),\n  fira('<==', [1614, 1614, 1517], [[0, 3]]),\n  fira('<=>', [1614, 1614, 1518], [[0, 3]]),\n  fira('=>', [1614, 1488], [[0, 2]]),\n  fira('==>', [1614, 1614, 1487], [[0, 3]]),\n  fira('=>>', [1614, 1614, 1489], [[0, 3]]),\n  fira('>=>', [1614, 1614, 1495], [[0, 3]]),\n  fira('>>=', [1614, 1614, 1498], [[0, 3]]),\n  fira('>>-', [1614, 1614, 1497], [[0, 3]]),\n  fira('>-', [1614, 1492], [[0, 2]]),\n  fira('<~>', [1614, 1614, 1526], [[0, 3]]),\n  fira('-<', [1614, 1066], [[0, 2]]),\n  fira('-<<', [1614, 1614, 1067], [[0, 3]]),\n  fira('=<<', [1614, 1614, 1490], [[0, 3]]),\n  fira('<~~', [1614, 1614, 1527], [[0, 3]]),\n  fira('<~', [1614, 1525], [[0, 2]]),\n  fira('~~', [1614, 1534], [[0, 2]]),\n  fira('~>', [1614, 1533], [[0, 2]]),\n  fira('~~>', [1614, 1614, 1535], [[0, 3]]),\n  fira('<<<', [1614, 1614, 1524], [[0, 3]]),\n  fira('<<', [1614, 1521], [[0, 2]]),\n  fira('<=', [1614, 1516], [[0, 2]]),\n  fira('<>', [1614, 1520], [[0, 2]]),\n  fira('>=', [1614, 1494], [[0, 2]]),\n  fira('>>', [1614, 1496], [[0, 2]]),\n  fira('>>>', [1614, 1614, 1499], [[0, 3]]),\n  fira('{.', [1001, 977], [[0, 2]]),\n  fira('{|', [1614, 1049], [[0, 2]]),\n  fira('[|', [1614, 1050], [[0, 2]]),\n  fira('<:', [1614, 1506], [[0, 2]]),\n  fira(':>', [1614, 1056], [[0, 2]]),\n  fira('|]', [1614, 1474], [[0, 2]]),\n  fira('|}', [1614, 1473], [[0, 2]]),\n  fira('.}', [977, 1002], [[0, 2]]),\n  fira('<|||', [1614, 1614, 1614, 1504], [[0, 4]]),\n  fira('<||', [1614, 1614, 1503], [[0, 3]]),\n  fira('<|', [1614, 1502], [[0, 2]]),\n  fira('<|>', [1614, 1614, 1505], [[0, 3]]),\n  fira('|>', [1614, 1477], [[0, 2]]),\n  fira('||>', [1614, 1614, 1472], [[0, 3]]),\n  fira('|||>', [1614, 1614, 1614, 1470], [[0, 4]]),\n  fira('<$', [1614, 1507], [[0, 2]]),\n  fira('<$>', [1614, 1614, 1508], [[0, 3]]),\n  fira('$>', [1614, 1479], [[0, 2]]),\n  fira('<+', [1614, 1514], [[0, 2]]),\n  fira('<+>', [1614, 1614, 1515], [[0, 3]]),\n  fira('+>', [1614, 1482], [[0, 2]]),\n  fira('<*', [1614, 1500], [[0, 2]]),\n  fira('<*>', [1614, 1614, 1501], [[0, 3]]),\n  fira('*>', [1614, 1047], [[0, 2]]),\n  fira('/*', [1614, 1092], [[0, 2]]),\n  fira('*/', [1614, 1048], [[0, 2]]),\n  fira('///', [1614, 1614, 1097], [[0, 3]]),\n  fira('//', [1614, 1096], [[0, 2]]),\n  fira('</', [1614, 1528], [[0, 2]]),\n  fira('<!--', [1614, 1614, 1614, 1509], [[0, 4]]),\n  fira('</>', [1614, 1614, 1529], [[0, 3]]),\n  fira('/>', [1614, 1095], [[0, 2]]),\n  fira('0xff', [895, 270, 166, 166], [[0, 3]]),\n  fira('10x10', [896, 895, 270, 896, 895], [[1, 4]]),\n  fira('9:45', [904, 998, 899, 900], [[0, 2]]),\n  fira('[:]', [1003, 998, 1004], [[0, 2]]),\n  fira(';;', [1614, 1091], [[0, 2]]),\n  fira('::', [1614, 1052], [[0, 2]]),\n  fira(':::', [1614, 1614, 1053], [[0, 3]]),\n  fira('..', [1614, 1082], [[0, 2]]),\n  fira('...', [1614, 1614, 1085], [[0, 3]]),\n  fira('..<', [1614, 1614, 1084], [[0, 3]]),\n  fira('!!', [1614, 1057], [[0, 2]]),\n  fira('??', [1614, 1090], [[0, 2]]),\n  fira('%%', [1614, 1536], [[0, 2]]),\n  fira('&&', [1614, 1468], [[0, 2]]),\n  fira('||', [1614, 1469], [[0, 2]]),\n  fira('?.', [1614, 1089], [[0, 2]]),\n  fira('?:', [1614, 1087], [[0, 2]]),\n  fira('++', [1614, 1480], [[0, 2]]),\n  fira('+++', [1614, 1614, 1481], [[0, 3]]),\n  fira('--', [1614, 1061], [[0, 2]]),\n  fira('---', [1614, 1614, 1062], [[0, 3]]),\n  fira('**', [1614, 1045], [[0, 2]]),\n  fira('***', [1614, 1614, 1046], [[0, 3]]),\n  fira('~=', [1614, 1532], [[0, 2]]),\n  fira('~-', [1614, 1531], [[0, 2]]),\n  fira('www', [1614, 1614, 271], [[0, 3]]),\n  fira('-~', [1614, 1068], [[0, 2]]),\n  fira('~@', [1614, 1530], [[0, 2]]),\n  fira('^=', [1614, 1478], [[0, 2]]),\n  fira('?=', [1614, 1088], [[0, 2]]),\n  fira('/=', [1614, 1093], [[0, 2]]),\n  fira('/==', [1614, 1614, 1094], [[0, 3]]),\n  fira('-|', [1614, 1060], [[0, 2]]),\n  fira('_|_', [1614, 1614, 1098], [[0, 3]]),\n  fira('|-', [1614, 1475], [[0, 2]]),\n  fira('|=', [1614, 1476], [[0, 2]]),\n  fira('||=', [1614, 1614, 1471], [[0, 3]]),\n  fira('#!', [1614, 1071], [[0, 2]]),\n  fira('#=', [1614, 1075], [[0, 2]]),\n  fira('##', [1614, 1072], [[0, 2]]),\n  fira('###', [1614, 1614, 1073], [[0, 3]]),\n  fira('####', [1614, 1614, 1614, 1074], [[0, 4]]),\n  fira('#{', [1614, 1069], [[0, 2]]),\n  fira('#[', [1614, 1070], [[0, 2]]),\n  fira(']#', [1614, 1051], [[0, 2]]),\n  fira('#(', [1614, 1076], [[0, 2]]),\n  fira('#?', [1614, 1077], [[0, 2]]),\n  fira('#_', [1614, 1078], [[0, 2]]),\n  fira('#_(', [1614, 1614, 1079], [[0, 3]]),\n  fira('::=', [1614, 1614, 1054], [[0, 3]]),\n  fira('.?', [1614, 1086], [[0, 2]]),\n  fira('===>', [1614, 1614, 1486, 1148], [[0, 4]])\n];\n\nconst iosevkaCases: ITestCase[] = [\n  iosevka('<-', [31, 3127], [[0, 2]]),\n  iosevka('<--', [31, 3129, 3139], [[0, 3]]),\n  iosevka('<---', [31, 3129, 3150, 3139], [[0, 4]]),\n  iosevka('<-----', [31, 3129, 3150, 3139, 3151, 3151], [[0, 6]]),\n  iosevka('->', [3126, 33], [[0, 2]]),\n  iosevka('-->', [3140, 3128, 33], [[0, 3]]),\n  iosevka('--->', [3140, 3150, 3128, 33], [[0, 4]]),\n  iosevka('----->', [3153, 3153, 3140, 3150, 3128, 33], [[0, 6]]),\n  iosevka('<->', [31, 3149, 33], [[0, 3]]),\n  iosevka('<-->', [31, 3129, 3128, 33], [[0, 4]]),\n  iosevka('<--->', [31, 3129, 3150, 3128, 33], [[0, 5]]),\n  iosevka('<----->', [31, 3129, 3150, 3150, 3150, 3128, 33], [[0, 7]]),\n  iosevka('<=', [3094, 3095], [[0, 2]]),\n  iosevka('<==', [31, 3158, 3168], [[0, 3]]),\n  iosevka('<===', [31, 3158, 3179, 3168], [[0, 4]]),\n  iosevka('<=====', [31, 3158, 3179, 3168, 3180, 3180], [[0, 6]]),\n  iosevka('=>', [3155, 33], [[0, 2]]),\n  iosevka('==>', [3169, 3157, 33], [[0, 3]]),\n  iosevka('===>', [3169, 3179, 3157, 33], [[0, 4]]),\n  iosevka('=====>', [3182, 3182, 3169, 3179, 3157, 33], [[0, 6]]),\n  iosevka('<=>', [31, 3178, 33], [[0, 3]]),\n  iosevka('<==>', [31, 3158, 3157, 33], [[0, 4]]),\n  iosevka('<===>', [31, 3158, 3179, 3157, 33], [[0, 5]]),\n  iosevka('<=====>', [31, 3158, 3179, 3179, 3179, 3157, 33], [[0, 7]]),\n  iosevka('<!--', [31, 3184, 3130, 3139], [[0, 4]]),\n  iosevka('<!---', [31, 3184, 3130, 3150, 3139], [[0, 5]]),\n  iosevka('<!-----', [31, 3184, 3130, 3150, 3139, 3151, 3151], [[0, 7]]),\n  iosevka('a:b', [68, 29, 69], []),\n  iosevka('a::b', [68, 3012, 3013, 69], [[1, 3]]),\n  iosevka('a:::b', [68, 3012, 3010, 3013, 69], [[1, 4]]),\n  iosevka(':=', [3011, 32], [[0, 2]]),\n  iosevka(':-', [3011, 16], [[0, 2]]),\n  iosevka(':+', [3011, 14], [[0, 2]]),\n  iosevka('=:', [32, 3011], [[0, 2]]),\n  iosevka('-:', [16, 3011], [[0, 2]]),\n  iosevka('+:', [14, 3011], [[0, 2]]),\n  iosevka('<*', [31, 3023], [[0, 2]]),\n  iosevka('*>', [3023, 33], [[0, 2]]),\n  iosevka('<*>', [31, 3023, 33], [[1, 3]]),\n  iosevka('<**>', [31, 3023, 3023, 33], [[1, 4]]),\n  iosevka('<****>', [31, 3023, 3023, 3023, 3023, 33], [[0, 3], [3, 6]]),\n  iosevka('==', [3083, 3084], [[0, 2]]),\n  iosevka('!=', [3098, 3084], [[0, 2]]),\n  iosevka('===', [3083, 3085, 3084], [[0, 3]]),\n  iosevka('!==', [3099, 3085, 3084], [[0, 3]]),\n  iosevka('====', [3083, 3085, 3085, 3084], [[0, 4]]),\n  iosevka('!===', [3100, 3085, 3085, 3084], [[0, 4]])\n];\n\nconst monoidCases: ITestCase[] = [\n  monoid('<!--', [775, 775, 775, 643], [[0, 4]]),\n  monoid('-->', [779, 779, 628], [[0, 3]]),\n  monoid('<--', [776, 776, 627], [[0, 3]]),\n  monoid('->>', [780, 780, 626], [[0, 3]]),\n  monoid('<<-', [777, 777, 625], [[0, 3]]),\n  monoid('->', [781, 623], [[0, 2]]),\n  monoid('<-', [778, 624], [[0, 2]]),\n  monoid('=>', [793, 666], [[0, 2]]),\n  monoid('<=>', [785, 785, 760], [[0, 3]]),\n  monoid('<==>', [786, 786, 786, 771], [[0, 4]]),\n  monoid('==>', [787, 787, 672], [[0, 3]]),\n  monoid('<==', [788, 788, 671], [[0, 3]]),\n  monoid('>>=', [791, 791, 758], [[0, 3]]),\n  monoid('=<<', [792, 792, 759], [[0, 3]]),\n  monoid('--', [667, 667], [[0, 2]]),\n  monoid(':=', [29, 761], [[0, 2]]),\n  monoid('=:=', [789, 789, 665], [[0, 3]]),\n  monoid('==', [794, 641], [[0, 2]]),\n  monoid('!==', [782, 782, 646], [[0, 3]]),\n  monoid('!=', [783, 629], [[0, 2]]),\n  monoid('<=', [790, 630], [[0, 2]]),\n  monoid('>=', [792, 631], [[0, 2]]),\n  monoid('//', [621, 664], [[0, 2]]),\n  monoid('/**', [18, 753, 753], [[0, 3]]),\n  monoid('/*', [18, 753], [[0, 2]]),\n  monoid('*/', [754, 18], [[0, 2]]),\n  monoid('&&', [633, 775], [[0, 2]]),\n  monoid('.&', [17, 755], [[0, 2]]),\n  monoid('||', [634, 635], [[0, 2]]),\n  monoid('!!', [769, 770], [[0, 2]]),\n  monoid('::', [772, 773], [[0, 2]]),\n  monoid('>>', [637, 638], [[0, 2]]),\n  monoid('<<', [639, 640], [[0, 2]]),\n  monoid('¯\\\\_(ツ)_/¯', [113, 765, 66, 767, 613, 768, 66, 766, 113], [[0, 3], [3, 6], [6, 9]]),\n  monoid('__', [763, 764], [[0, 2]])\n];\n\nconst ubuntuCases: ITestCase[] = [\n  ubuntu('==>', [32, 32, 33], [])\n];\n\nconst fontPaths: Record<string, string> = {\n  'Fira Code': path.join(__dirname, '../../fonts/FiraCode-Regular.otf'),\n  'Iosevka': path.join(__dirname, '../../fonts/iosevka-regular.ttf'),\n  'Monoid': path.join(__dirname, '../../fonts/Monoid-Regular.ttf'),\n  'Ubuntu Mono': path.join(__dirname, '../../fonts/UbuntuMono-Regular.ttf')\n};\n\nconst fontCache: Map<string, IFont> = new Map();\n\nfunction loadFont(fontName: string): IFont {\n  let font = fontCache.get(fontName);\n  if (!font) {\n    const fontPath = fontPaths[fontName];\n    const buffer = fs.readFileSync(fontPath);\n    font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));\n    fontCache.set(fontName, font);\n  }\n  return font;\n}\n\ndescribe('addon-ligatures - index', () => {\n  describe('findLigatures', () => {\n    for (const { font: fontName, input, glyphs, ranges } of [...firaCases, ...iosevkaCases, ...monoidCases, ...ubuntuCases]) {\n      it(`${fontName}: '${input}'`, () => {\n        const font = loadFont(fontName);\n        const result = font.findLigatures(input);\n        assert.deepEqual(result.outputGlyphs, glyphs);\n        assert.deepEqual(result.contextRanges, ranges);\n      });\n    }\n  });\n\n  describe('findLigatureRanges', () => {\n    for (const { font: fontName, input, ranges } of [...firaCases, ...iosevkaCases, ...monoidCases, ...ubuntuCases]) {\n      it(`${fontName}: '${input}'`, () => {\n        const font = loadFont(fontName);\n        const result = font.findLigatureRanges(input);\n        assert.deepEqual(result, ranges);\n      });\n    }\n  });\n\n  describe('caching', () => {\n    it('findLigatures caches successive calls correctly', () => {\n      const fontPath = fontPaths['Fira Code'];\n      const buffer = fs.readFileSync(fontPath);\n      const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });\n      const result1 = font.findLigatures('in --> out');\n      const result2 = font.findLigatures('in --> out');\n      assert.deepEqual(result1, result2);\n    });\n\n    it('findLigatureRanges caches successive calls correctly', () => {\n      const fontPath = fontPaths['Fira Code'];\n      const buffer = fs.readFileSync(fontPath);\n      const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });\n      const result1 = font.findLigatureRanges('in --> out');\n      const result2 = font.findLigatureRanges('in --> out');\n      assert.deepEqual(result1, result2);\n    });\n\n    it('caches calls to findLigatures after findLigatureRanges correctly', () => {\n      const fontPath = fontPaths['Fira Code'];\n      const buffer = fs.readFileSync(fontPath);\n\n      const uncached = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));\n      const uncachedResult1 = uncached.findLigatureRanges('in --> out');\n      const uncachedResult2 = uncached.findLigatures('in --> out');\n\n      const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });\n      const result1 = font.findLigatureRanges('in --> out');\n      const result2 = font.findLigatures('in --> out');\n\n      assert.deepEqual(result1, uncachedResult1);\n      assert.deepEqual(result2, uncachedResult2);\n      assert.deepEqual(result1, result2.contextRanges);\n    });\n\n    it('caches calls to findLigatureRanges after findLigatures correctly', () => {\n      const fontPath = fontPaths['Fira Code'];\n      const buffer = fs.readFileSync(fontPath);\n\n      const uncached = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));\n      const uncachedResult1 = uncached.findLigatures('in --> out');\n      const uncachedResult2 = uncached.findLigatureRanges('in --> out');\n\n      const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });\n      const result1 = font.findLigatures('in --> out');\n      const result2 = font.findLigatureRanges('in --> out');\n\n      assert.deepEqual(result1, uncachedResult1);\n      assert.deepEqual(result2, uncachedResult2);\n      assert.deepEqual(result1.contextRanges, result2);\n    });\n  });\n});"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/index.ts",
    "content": "import * as opentype from 'opentype.js';\nimport LRUCache = require('lru-cache');\n\nimport { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';\nimport mergeTrees from './merge';\nimport walkTree from './walk';\nimport mergeRange from './mergeRange';\n\nimport buildTreeGsubType6Format1 from './processors/6-1';\nimport buildTreeGsubType6Format2 from './processors/6-2';\nimport buildTreeGsubType6Format3 from './processors/6-3';\nimport buildTreeGsubType8Format1 from './processors/8-1';\nimport flatten from './flatten';\n\nclass FontImpl implements IFont {\n  private _font: opentype.Font;\n  private _lookupTrees: { tree: IFlattenedLookupTree, processForward: boolean }[] = [];\n  private _glyphLookups: { [glyphId: string]: number[] } = {};\n  private _cache?: LRUCache<string, ILigatureData | [number, number][]>;\n\n  constructor(font: opentype.Font, options: Required<IOptions>) {\n    this._font = font;\n\n    if (options.cacheSize > 0) {\n      this._cache = new LRUCache({\n        max: options.cacheSize,\n        length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any\n      });\n    }\n\n    const caltFeatures = this._font.tables.gsub && this._font.tables.gsub.features.filter((f: { tag: string }) => f.tag === 'calt') || [];\n    const lookupIndices: number[] = caltFeatures\n      .reduce((acc: number[], val: { feature: { lookupListIndexes: number[] } }) => [...acc, ...val.feature.lookupListIndexes], []);\n\n    const allLookups = this._font.tables.gsub && this._font.tables.gsub.lookups || [];\n    const lookupGroups = allLookups.filter((l: unknown, i: number) => lookupIndices.some(idx => idx === i));\n\n    for (const [index, lookup] of lookupGroups.entries()) {\n      const trees: ILookupTree[] = [];\n      switch (lookup.lookupType) {\n        case 6:\n          for (const [index, table] of lookup.subtables.entries()) {\n            switch (table.substFormat) {\n              case 1:\n                trees.push(buildTreeGsubType6Format1(table, allLookups, index));\n                break;\n              case 2:\n                trees.push(buildTreeGsubType6Format2(table, allLookups, index));\n                break;\n              case 3:\n                trees.push(buildTreeGsubType6Format3(table, allLookups, index));\n                break;\n            }\n          }\n          break;\n        case 8:\n          for (const [index, table] of lookup.subtables.entries()) {\n            trees.push(buildTreeGsubType8Format1(table, index));\n          }\n          break;\n      }\n\n      const tree = flatten(mergeTrees(trees));\n\n      this._lookupTrees.push({\n        tree,\n        processForward: lookup.lookupType !== 8\n      });\n\n      for (const glyphId of Object.keys(tree)) {\n        if (!this._glyphLookups[glyphId]) {\n          this._glyphLookups[glyphId] = [];\n        }\n\n        this._glyphLookups[glyphId].push(index);\n      }\n    }\n  }\n\n  public findLigatures(text: string): ILigatureData {\n    const cached = this._cache && this._cache.get(text);\n    if (cached && !Array.isArray(cached)) {\n      return cached;\n    }\n\n    const glyphIds: number[] = [];\n    for (const char of text) {\n      glyphIds.push(this._font.charToGlyphIndex(char));\n    }\n\n    // If there are no lookup groups, there's no point looking for\n    // replacements. This gives us a minor performance boost for fonts with\n    // no ligatures\n    if (this._lookupTrees.length === 0) {\n      return {\n        inputGlyphs: glyphIds,\n        outputGlyphs: glyphIds,\n        contextRanges: []\n      };\n    }\n\n    const result = this._findInternal(glyphIds.slice());\n    const finalResult: ILigatureData = {\n      inputGlyphs: glyphIds,\n      outputGlyphs: result.sequence,\n      contextRanges: result.ranges\n    };\n    if (this._cache) {\n      this._cache.set(text, finalResult);\n    }\n\n    return finalResult;\n  }\n\n  public findLigatureRanges(text: string): [number, number][] {\n    // Short circuit the process if there are no possible ligatures in the\n    // font\n    if (this._lookupTrees.length === 0) {\n      return [];\n    }\n\n    const cached = this._cache && this._cache.get(text);\n    if (cached) {\n      return Array.isArray(cached) ? cached : cached.contextRanges;\n    }\n\n    const glyphIds: number[] = [];\n    for (const char of text) {\n      glyphIds.push(this._font.charToGlyphIndex(char));\n    }\n\n    const result = this._findInternal(glyphIds);\n    if (this._cache) {\n      this._cache.set(text, result.ranges);\n    }\n\n    return result.ranges;\n  }\n\n  private _findInternal(sequence: number[]): { sequence: number[], ranges: [number, number][] } {\n    const ranges: [number, number][] = [];\n\n    let nextLookup = this._getNextLookup(sequence, 0);\n    while (nextLookup.index !== null) {\n      const lookup = this._lookupTrees[nextLookup.index];\n      if (lookup.processForward) {\n        let lastGlyphIndex = nextLookup.last;\n        for (let i = nextLookup.first; i < lastGlyphIndex; i++) {\n          const result = walkTree(lookup.tree, sequence, i, i);\n          if (result) {\n            for (let j = 0; j < result.substitutions.length; j++) {\n              const sub = result.substitutions[j];\n              if (sub !== null) {\n                sequence[i + j] = sub;\n              }\n            }\n\n            mergeRange(\n              ranges,\n              result.contextRange[0] + i,\n              result.contextRange[1] + i\n            );\n\n            // Substitutions can end up extending the search range\n            if (i + result.length >= lastGlyphIndex) {\n              lastGlyphIndex = i + result.length + 1;\n            }\n\n            i += result.length - 1;\n          }\n        }\n      } else {\n        // We don't need to do the lastGlyphIndex tracking here because\n        // reverse processing isn't allowed to replace more than one\n        // character at a time.\n        for (let i = nextLookup.last - 1; i >= nextLookup.first; i--) {\n          const result = walkTree(lookup.tree, sequence, i, i);\n          if (result) {\n            for (let j = 0; j < result.substitutions.length; j++) {\n              const sub = result.substitutions[j];\n              if (sub !== null) {\n                sequence[i + j] = sub;\n              }\n            }\n\n            mergeRange(\n              ranges,\n              result.contextRange[0] + i,\n              result.contextRange[1] + i\n            );\n\n            i -= result.length - 1;\n          }\n        }\n      }\n\n      nextLookup = this._getNextLookup(sequence, nextLookup.index + 1);\n    }\n\n    return { sequence, ranges };\n  }\n\n  /**\n   * Returns the lookup and glyph range for the first lookup that might\n   * contain a match.\n   *\n   * @param sequence Input glyph sequence\n   * @param start The first input to try\n   */\n  private _getNextLookup(sequence: number[], start: number): { index: number | null, first: number, last: number } {\n    const result: { index: number | null, first: number, last: number } = {\n      index: null,\n      first: Infinity,\n      last: -1\n    };\n\n    // Loop through each glyph and find the first valid lookup for it\n    for (let i = 0; i < sequence.length; i++) {\n      const lookups = this._glyphLookups[sequence[i]];\n      if (!lookups) {\n        continue;\n      }\n\n      for (let j = 0; j < lookups.length; j++) {\n        const lookupIndex = lookups[j];\n        if (lookupIndex >= start) {\n          // Update the lookup information if it's the one we're\n          // storing or earlier than it.\n          if (result.index === null || lookupIndex <= result.index) {\n            result.index = lookupIndex;\n\n            if (result.first > i) {\n              result.first = i;\n            }\n\n            result.last = i + 1;\n          }\n\n          break;\n        }\n      }\n    }\n\n    return result;\n  }\n}\n\n/**\n * Load the font from it's binary data. The returned value can be used to find\n * ligatures for the font.\n *\n * @param buffer ArrayBuffer of the font to load\n */\nexport function loadBuffer(buffer: ArrayBuffer, options?: IOptions): IFont {\n  const font = opentype.parse(buffer);\n  return new FontImpl(font, {\n    cacheSize: 0,\n    ...options\n  });\n}\n\nexport { IFont as Font, ILigatureData as LigatureData, IOptions as Options };\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/merge.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport mergeTrees from './merge';\n\ninterface ILookupResult {\n  contextRange: [number, number];\n  index: number;\n  subIndex: number;\n  length: number;\n  substitutions: number[];\n}\n\nfunction lookup(substitutionGlyph: number, index?: number, subIndex?: number): ILookupResult {\n  return {\n    contextRange: [0, 1],\n    index: index || 0,\n    subIndex: subIndex || 0,\n    length: 1,\n    substitutions: [substitutionGlyph]\n  };\n}\n\ndescribe('addon-ligatures - merge', () => {\n  describe('mergeTrees', () => {\n    it('combines disjoint trees', () => {\n      const result = mergeTrees([\n        {\n          individual: {\n            '1': { lookup: lookup(1) }\n          },\n          range: []\n        },\n        {\n          individual: {},\n          range: [{\n            entry: { lookup: lookup(2) },\n            range: [2, 4]\n          }]\n        },\n        {\n          individual: {\n            '5': { lookup: lookup(3) }\n          },\n          range: []\n        },\n        {\n          individual: {},\n          range: [{\n            entry: { lookup: lookup(4) },\n            range: [8, 10]\n          }]\n        }\n      ]);\n\n      assert.deepEqual(result, {\n        individual: {\n          '1': { lookup: lookup(1) },\n          '5': { lookup: lookup(3) }\n        },\n        range: [{\n          entry: { lookup: lookup(2) },\n          range: [2, 4]\n        }, {\n          entry: { lookup: lookup(4) },\n          range: [8, 10]\n        }]\n      });\n    });\n\n    it('merges matching individual glyphs', () => {\n      const result = mergeTrees([\n        {\n          individual: {\n            '1': { lookup: lookup(1, 1) }\n          },\n          range: []\n        },\n        {\n          individual: {\n            '1': { lookup: lookup(2, 0) }\n          },\n          range: []\n        },\n        {\n          individual: {\n            '1': { lookup: lookup(3, 2) }\n          },\n          range: []\n        }\n      ]);\n\n      assert.deepEqual(result, {\n        individual: {\n          '1': { lookup: lookup(2, 0) }\n        },\n        range: []\n      });\n    });\n\n    it('merges range glyphs overlapping individual glyphs', () => {\n      const result = mergeTrees([\n        {\n          individual: {\n            '1': { lookup: lookup(1, 0) }\n          },\n          range: []\n        },\n        {\n          individual: {},\n          range: [{\n            entry: { lookup: lookup(2, 1) },\n            range: [0, 4]\n          }]\n        }\n      ]);\n\n      assert.deepEqual(result, {\n        individual: {\n          '0': { lookup: lookup(2, 1) },\n          '1': { lookup: lookup(1, 0) }\n        },\n        range: [{\n          entry: { lookup: lookup(2, 1) },\n          range: [2, 4]\n        }]\n      });\n    });\n\n    it('merges individual glyphs overlapping range glyphs', () => {\n      const result = mergeTrees([\n        {\n          individual: {},\n          range: [{\n            entry: { lookup: lookup(2, 1) },\n            range: [0, 4]\n          }]\n        },\n        {\n          individual: {\n            '1': { lookup: lookup(1, 0) }\n          },\n          range: []\n        }\n      ]);\n\n      assert.deepEqual(result, {\n        individual: {\n          '0': { lookup: lookup(2, 1) },\n          '1': { lookup: lookup(1, 0) }\n        },\n        range: [{\n          entry: { lookup: lookup(2, 1) },\n          range: [2, 4]\n        }]\n      });\n    });\n\n    it('merges multiple overlapping ranges', () => {\n      const result = mergeTrees([\n        {\n          individual: {},\n          range: [{\n            entry: { lookup: lookup(1, 2) },\n            range: [0, 3]\n          }, {\n            entry: { lookup: lookup(2, 1) },\n            range: [6, 12]\n          }, {\n            entry: { lookup: lookup(5, 3) },\n            range: [15, 20]\n          }, {\n            entry: { lookup: lookup(7, 4) },\n            range: [20, 22]\n          }]\n        },\n        {\n          individual: {},\n          range: [{\n            entry: { lookup: lookup(3, 0) },\n            range: [2, 8]\n          }, {\n            entry: { lookup: lookup(4, 0) },\n            range: [10, 13]\n          }, {\n            entry: { lookup: lookup(6, 0) },\n            range: [16, 21]\n          }]\n        }\n      ]);\n\n      assert.deepEqual(result, {\n        individual: {\n          '2': { lookup: lookup(3, 0) },\n          '12': { lookup: lookup(4, 0) },\n          '15': { lookup: lookup(5, 3) },\n          '20': { lookup: lookup(6, 0) },\n          '21': { lookup: lookup(7, 4) }\n        },\n        range: [{\n          entry: { lookup: lookup(1, 2) },\n          range: [0, 2]\n        }, {\n          entry: { lookup: lookup(3, 0) },\n          range: [6, 8]\n        }, {\n          entry: { lookup: lookup(3, 0) },\n          range: [3, 6]\n        }, {\n          entry: { lookup: lookup(2, 1) },\n          range: [8, 10]\n        }, {\n          entry: { lookup: lookup(4, 0) },\n          range: [10, 12]\n        }, {\n          entry: { lookup: lookup(6, 0) },\n          range: [16, 20]\n        }]\n      });\n    });\n  });\n});"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/merge.ts",
    "content": "import { ILookupTree, ILookupTreeEntry } from './types';\n\n/**\n * Merges the provided trees into a single lookup tree. When conflicting lookups\n * are encountered between two trees, the one with the lower index, then the\n * lower subindex is chosen.\n *\n * @param trees Array of trees to merge. Entries in earlier trees are favored\n *              over those in later trees when there is a choice.\n */\nexport default function mergeTrees(trees: ILookupTree[]): ILookupTree {\n  const result: ILookupTree = {\n    individual: {},\n    range: []\n  };\n\n  const mergedEntries = new WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>();\n  for (const tree of trees) {\n    mergeSubtree(result, tree, mergedEntries);\n  }\n\n  return result;\n}\n\n/**\n * Recursively merges the data for the mergeTree into the mainTree.\n *\n * @param mainTree The tree where the values should be merged\n * @param mergeTree The tree to be merged into the mainTree\n * @param mergedEntries WeakMap to track already merged entry pairs\n */\nfunction mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {\n  // Need to fix this recursively (and handle lookups)\n  for (const [glyphId, value] of Object.entries(mergeTree.individual)) {\n    // The main tree is guaranteed to have no overlaps between the\n    // individual and range values, so if we match an invididual, there\n    // must not be a range\n    if (mainTree.individual[glyphId]) {\n      mergeTreeEntry(mainTree.individual[glyphId], value, mergedEntries);\n    } else {\n      let matched = false;\n      for (const [index, { range, entry }] of mainTree.range.entries()) {\n        const overlap = getIndividualOverlap(Number(glyphId), range);\n\n        // Don't overlap\n        if (overlap.both === null) {\n          continue;\n        }\n\n        matched = true;\n\n        // If they overlap, we have to split the range and then\n        // merge the overlap\n        mainTree.individual[glyphId] = value;\n        mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);\n\n        // When there's an overlap, we also have to fix up the range\n        // that we had already processed\n        mainTree.range.splice(index, 1);\n        for (const glyph of overlap.second) {\n          if (Array.isArray(glyph)) {\n            mainTree.range.push({\n              range: glyph,\n              entry: cloneEntry(entry)\n            });\n          } else {\n            mainTree.individual[glyph] = cloneEntry(entry);\n          }\n        }\n      }\n\n      if (!matched) {\n        mainTree.individual[glyphId] = value;\n      }\n    }\n  }\n\n  for (const { range, entry } of mergeTree.range) {\n    // Ranges are more complicated, because they can overlap with\n    // multiple things, individual and range alike. We start by\n    // eliminating ranges that are already present in another range\n    let remainingRanges: (number | [number, number])[] = [range];\n\n    for (let index = 0; index < mainTree.range.length; index++) {\n      const { range, entry: resultEntry } = mainTree.range[index];\n      for (const [remainingIndex, remainingRange] of remainingRanges.entries()) {\n        if (Array.isArray(remainingRange)) {\n          const overlap = getRangeOverlap(remainingRange, range);\n          if (overlap.both === null) {\n            continue;\n          }\n\n          mainTree.range.splice(index, 1);\n          index--;\n\n          const entryToMerge: ILookupTreeEntry = cloneEntry(resultEntry);\n          if (Array.isArray(overlap.both)) {\n            mainTree.range.push({\n              range: overlap.both,\n              entry: entryToMerge\n            });\n          } else {\n            mainTree.individual[overlap.both] = entryToMerge;\n          }\n\n          mergeTreeEntry(entryToMerge, cloneEntry(entry), mergedEntries);\n\n          for (const second of overlap.second) {\n            if (Array.isArray(second)) {\n              mainTree.range.push({\n                range: second,\n                entry: cloneEntry(resultEntry)\n              });\n            } else {\n              mainTree.individual[second] = cloneEntry(resultEntry);\n            }\n          }\n\n          remainingRanges = overlap.first;\n        } else {\n          const overlap = getIndividualOverlap(remainingRange, range);\n          if (overlap.both === null) {\n            continue;\n          }\n\n          // If they overlap, we have to split the range and then\n          // merge the overlap\n          mainTree.individual[remainingRange] = cloneEntry(entry);\n          mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry), mergedEntries);\n\n          // When there's an overlap, we also have to fix up the range\n          // that we had already processed\n          mainTree.range.splice(index, 1);\n          index--;\n\n          for (const glyph of overlap.second) {\n            if (Array.isArray(glyph)) {\n              mainTree.range.push({\n                range: glyph,\n                entry: cloneEntry(resultEntry)\n              });\n            } else {\n              mainTree.individual[glyph] = cloneEntry(resultEntry);\n            }\n          }\n\n          remainingRanges.splice(remainingIndex, 1, ...overlap.first);\n          break;\n        }\n      }\n    }\n\n    // Next, we run the same against any individual glyphs\n    for (const glyphId of Object.keys(mainTree.individual)) {\n      for (const [remainingIndex, remainingRange] of remainingRanges.entries()) {\n        if (Array.isArray(remainingRange)) {\n          const overlap = getIndividualOverlap(Number(glyphId), remainingRange);\n          if (overlap.both === null) {\n            continue;\n          }\n\n          // If they overlap, we have to merge the overlap\n          mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);\n\n          // Update the remaining ranges\n          remainingRanges.splice(remainingIndex, 1, ...overlap.second);\n          break;\n        } else {\n          if (Number(glyphId) === remainingRange) {\n            mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);\n            break;\n          }\n        }\n      }\n    }\n\n    // Any remaining ranges should just be added directly\n    for (const remainingRange of remainingRanges) {\n      if (Array.isArray(remainingRange)) {\n        mainTree.range.push({\n          range: remainingRange,\n          entry: cloneEntry(entry)\n        });\n      } else {\n        mainTree.individual[remainingRange] = cloneEntry(entry);\n      }\n    }\n  }\n}\n\n/**\n * Recursively merges the entry forr the mergeTree into the mainTree\n *\n * @param mainTree The entry where the values should be merged\n * @param mergeTree The entry to merge into the mainTree\n * @param mergedEntries WeakMap to track already merged entry pairs\n */\nfunction mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {\n  // Check if we've already merged this pair\n  let mergedSet = mergedEntries.get(mainTree);\n  if (mergedSet?.has(mergeTree)) {\n    return;\n  }\n  if (!mergedSet) {\n    mergedSet = new Set();\n    mergedEntries.set(mainTree, mergedSet);\n  }\n  mergedSet.add(mergeTree);\n\n  if (\n    mergeTree.lookup && (\n      !mainTree.lookup ||\n      mainTree.lookup.index > mergeTree.lookup.index ||\n      (mainTree.lookup.index === mergeTree.lookup.index && mainTree.lookup.subIndex > mergeTree.lookup.subIndex)\n    )\n  ) {\n    mainTree.lookup = mergeTree.lookup;\n  }\n\n  if (mergeTree.forward) {\n    if (!mainTree.forward) {\n      mainTree.forward = mergeTree.forward;\n    } else {\n      mergeSubtree(mainTree.forward, mergeTree.forward, mergedEntries);\n    }\n  }\n\n  if (mergeTree.reverse) {\n    if (!mainTree.reverse) {\n      mainTree.reverse = mergeTree.reverse;\n    } else {\n      mergeSubtree(mainTree.reverse, mergeTree.reverse, mergedEntries);\n    }\n  }\n}\n\ninterface IOverlap {\n  first: (number | [number, number])[];\n  second: (number | [number, number])[];\n  both: number | [number, number] | null;\n}\n\n/**\n * Determines the overlap (if any) between two ranges. Returns the distinct\n * ranges for each range and the overlap (if any).\n *\n * @param first First range\n * @param second Second range\n */\nfunction getRangeOverlap(first: [number, number], second: [number, number]): IOverlap {\n  const result: IOverlap = {\n    first: [],\n    second: [],\n    both: null\n  };\n\n  // Both\n  if (first[0] < second[1] && second[0] < first[1]) {\n    const start = Math.max(first[0], second[0]);\n    const end = Math.min(first[1], second[1]);\n    result.both = rangeOrIndividual(start, end);\n  }\n\n  // Before\n  if (first[0] < second[0]) {\n    const start = first[0];\n    const end = Math.min(second[0], first[1]);\n    result.first.push(rangeOrIndividual(start, end));\n  } else if (second[0] < first[0]) {\n    const start = second[0];\n    const end = Math.min(second[1], first[0]);\n    result.second.push(rangeOrIndividual(start, end));\n  }\n\n  // After\n  if (first[1] > second[1]) {\n    const start = Math.max(first[0], second[1]);\n    const end = first[1];\n    result.first.push(rangeOrIndividual(start, end));\n  } else if (second[1] > first[1]) {\n    const start = Math.max(first[1], second[0]);\n    const end = second[1];\n    result.second.push(rangeOrIndividual(start, end));\n  }\n\n  return result;\n}\n\n/**\n * Determines the overlap (if any) between the individual glyph and the range\n * provided. Returns the glyphs and/or ranges that are unique to each provided\n * and the overlap (if any).\n *\n * @param first Individual glyph\n * @param second Range\n */\nfunction getIndividualOverlap(first: number, second: [number, number]): IOverlap {\n  // Disjoint\n  if (first < second[0] || first > second[1]) {\n    return {\n      first: [first],\n      second: [second],\n      both: null\n    };\n  }\n\n  const result: IOverlap = {\n    first: [],\n    second: [],\n    both: first\n  };\n\n  if (second[0] < first) {\n    result.second.push(rangeOrIndividual(second[0], first));\n  }\n\n  if (second[1] > first) {\n    result.second.push(rangeOrIndividual(first + 1, second[1]));\n  }\n\n  return result;\n}\n\n/**\n * Returns an individual glyph if the range is of size one or a range if it is\n * larger.\n *\n * @param start Beginning of the range (inclusive)\n * @param end End of the range (exclusive)\n */\nfunction rangeOrIndividual(start: number, end: number): number | [number, number] {\n  if (end - start === 1) {\n    return start;\n  }\n  return [start, end];\n\n}\n\n/**\n * Clones an individual lookup tree entry.\n *\n * @param entry Lookup tree entry to clone\n * @param visited Map to track already cloned entries (prevents infinite loops)\n */\nfunction cloneEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, ILookupTreeEntry> = new Map()): ILookupTreeEntry {\n  if (visited.has(entry)) {\n    return visited.get(entry)!;\n  }\n\n  const result: ILookupTreeEntry = {};\n  visited.set(entry, result);\n\n  if (entry.forward) {\n    result.forward = cloneTree(entry.forward, visited);\n  }\n\n  if (entry.reverse) {\n    result.reverse = cloneTree(entry.reverse, visited);\n  }\n\n  if (entry.lookup) {\n    result.lookup = {\n      contextRange: entry.lookup.contextRange.slice() as [number, number],\n      index: entry.lookup.index,\n      length: entry.lookup.length,\n      subIndex: entry.lookup.subIndex,\n      substitutions: entry.lookup.substitutions.slice()\n    };\n  }\n\n  return result;\n}\n\n/**\n * Clones a lookup tree.\n *\n * @param tree Lookup tree to clone\n * @param visited Map to track already cloned entries (prevents infinite loops)\n */\nfunction cloneTree(tree: ILookupTree, visited: Map<ILookupTreeEntry, ILookupTreeEntry> = new Map()): ILookupTree {\n  const individual: { [glyphId: string]: ILookupTreeEntry } = {};\n  for (const [glyphId, entry] of Object.entries(tree.individual)) {\n    individual[glyphId] = cloneEntry(entry, visited);\n  }\n\n  return {\n    individual,\n    range: tree.range.map(({ range, entry }) => ({\n      range: range.slice() as [number, number],\n      entry: cloneEntry(entry, visited)\n    }))\n  };\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/mergeRange.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport mergeRange from './mergeRange';\n\ndescribe('addon-ligatures - mergeRange', () => {\n  it('inserts a new range before the existing ones', () => {\n    const result = mergeRange([[1, 2], [2, 3]], 0, 1);\n    assert.deepEqual(result, [[0, 1], [1, 2], [2, 3]]);\n  });\n\n  it('inserts in between two ranges', () => {\n    const result = mergeRange([[0, 2], [4, 6]], 2, 4);\n    assert.deepEqual(result, [[0, 2], [2, 4], [4, 6]]);\n  });\n\n  it('inserts after the last range', () => {\n    const result = mergeRange([[0, 2], [4, 6]], 6, 8);\n    assert.deepEqual(result, [[0, 2], [4, 6], [6, 8]]);\n  });\n\n  it('extends the beginning of a range', () => {\n    const result = mergeRange([[0, 2], [4, 6]], 3, 5);\n    assert.deepEqual(result, [[0, 2], [3, 6]]);\n  });\n\n  it('extends the end of a range', () => {\n    const result = mergeRange([[0, 2], [4, 6]], 1, 4);\n    assert.deepEqual(result, [[0, 4], [4, 6]]);\n  });\n\n  it('extends the last range', () => {\n    const result = mergeRange([[0, 2], [4, 6]], 5, 7);\n    assert.deepEqual(result, [[0, 2], [4, 7]]);\n  });\n\n  it('connects two ranges', () => {\n    const result = mergeRange([[0, 2], [4, 6]], 1, 5);\n    assert.deepEqual(result, [[0, 6]]);\n  });\n\n  it('connects more than two ranges', () => {\n    const result = mergeRange([[0, 2], [4, 6], [8, 10], [12, 14]], 1, 10);\n    assert.deepEqual(result, [[0, 10], [12, 14]]);\n  });\n});\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/mergeRange.ts",
    "content": "/**\n * Merges the range defined by the provided start and end into the list of\n * existing ranges. The merge is done in place on the existing range for\n * performance and is also returned.\n *\n * @param ranges Existing range list\n * @param newRangeStart Start position of the range to merge, inclusive\n * @param newRangeEnd End position of range to merge, exclusive\n */\nexport default function mergeRange(ranges: [number, number][], newRangeStart: number, newRangeEnd: number): [number, number][] {\n  let inRange = false;\n  for (let i = 0; i < ranges.length; i++) {\n    const range = ranges[i];\n    if (!inRange) {\n      if (newRangeEnd <= range[0]) {\n        // Case 1: New range is before the search range\n        ranges.splice(i, 0, [newRangeStart, newRangeEnd]);\n        return ranges;\n      }\n      if (newRangeEnd <= range[1]) {\n        // Case 2: New range is either wholly contained within the\n        // search range or overlaps with the front of it\n        range[0] = Math.min(newRangeStart, range[0]);\n        return ranges;\n      }\n      if (newRangeStart < range[1]) {\n        // Case 3: New range either wholly contains the search range\n        // or overlaps with the end of it\n        range[0] = Math.min(newRangeStart, range[0]);\n        inRange = true;\n      } else {\n        // Case 4: New range starts after the search range\n        continue;\n      }\n    } else {\n      if (newRangeEnd <= range[0]) {\n        // Case 5: New range extends from previous range but doesn't\n        // reach the current one\n        ranges[i - 1][1] = newRangeEnd;\n        return ranges;\n      }\n      if (newRangeEnd <= range[1]) {\n        // Case 6: New range extends from prvious range into the\n        // current range\n        ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);\n        ranges.splice(i, 1);\n        inRange = false;\n        return ranges;\n      }\n      // Case 7: New range extends from previous range past the\n      // end of the current range\n      ranges.splice(i, 1);\n      i--;\n    }\n  }\n\n  if (inRange) {\n    // Case 8: New range extends past the last existing range\n    ranges[ranges.length - 1][1] = newRangeEnd;\n  } else {\n    // Case 9: New range starts after the last existing range\n    ranges.push([newRangeStart, newRangeEnd]);\n  }\n\n  return ranges;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/6-1.ts",
    "content": "import { ChainingContextualSubstitutionTable, Lookup } from '../tables';\nimport { ILookupTree } from '../types';\n\nimport { listGlyphsByIndex } from './coverage';\nimport { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, IEntryMeta } from './helper';\n\n/**\n * Build lookup tree for GSUB lookup table 6, format 1.\n * https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#61-chaining-context-substitution-format-1-simple-glyph-contexts\n *\n * @param table JSON representation of the table\n * @param lookups List of lookup tables\n * @param tableIndex Index of this table in the overall lookup\n */\nexport default function buildTree(table: ChainingContextualSubstitutionTable.IFormat1, lookups: Lookup[], tableIndex: number): ILookupTree {\n  const result: ILookupTree = {\n    individual: {},\n    range: []\n  };\n\n  const firstGlyphs = listGlyphsByIndex(table.coverage);\n\n  for (const { glyphId, index } of firstGlyphs) {\n    const chainRuleSet = table.chainRuleSets[index];\n\n    // If the chain rule set is null there's nothing to do with this table.\n    if (!chainRuleSet) {\n      continue;\n    }\n\n    for (const [subIndex, subTable] of chainRuleSet.entries()) {\n      let currentEntries: IEntryMeta[] = getInputTree(\n        result,\n        subTable.lookupRecords,\n        lookups,\n        0,\n        glyphId\n      ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] }));\n\n      // We walk forward, then backward\n      for (const [index, glyph] of subTable.input.entries()) {\n        currentEntries = processInputPosition(\n          [glyph],\n          index + 1,\n          currentEntries,\n          subTable.lookupRecords,\n          lookups\n        );\n      }\n\n      for (const glyph of subTable.lookahead) {\n        currentEntries = processLookaheadPosition(\n          [glyph],\n          currentEntries\n        );\n      }\n\n      for (const glyph of subTable.backtrack) {\n        currentEntries = processBacktrackPosition(\n          [glyph],\n          currentEntries\n        );\n      }\n\n      // When we get to the end, insert the lookup information\n      for (const { entry, substitutions } of currentEntries) {\n        entry.lookup = {\n          substitutions,\n          length: subTable.input.length + 1,\n          index: tableIndex,\n          subIndex,\n          contextRange: [\n            -1 * subTable.backtrack.length,\n            1 + subTable.input.length + subTable.lookahead.length\n          ]\n        };\n      }\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/6-2.ts",
    "content": "import { ChainingContextualSubstitutionTable, Lookup } from '../tables';\nimport { ILookupTree } from '../types';\nimport mergeTrees from '../merge';\n\nimport { listGlyphsByIndex } from './coverage';\nimport getGlyphClass, { listClassGlyphs } from './classDef';\nimport { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, IEntryMeta } from './helper';\n\n/**\n * Build lookup tree for GSUB lookup table 6, format 2.\n * https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#62-chaining-context-substitution-format-2-class-based-glyph-contexts\n *\n * @param table JSON representation of the table\n * @param lookups List of lookup tables\n * @param tableIndex Index of this table in the overall lookup\n */\nexport default function buildTree(table: ChainingContextualSubstitutionTable.IFormat2, lookups: Lookup[], tableIndex: number): ILookupTree {\n  const results: ILookupTree[] = [];\n\n  const firstGlyphs = listGlyphsByIndex(table.coverage);\n\n  for (const { glyphId } of firstGlyphs) {\n    const firstInputClass = getGlyphClass(table.inputClassDef, glyphId);\n    for (const [glyphId, inputClass] of firstInputClass.entries()) {\n      // istanbul ignore next - invalid font\n      if (inputClass === null) {\n        continue;\n      }\n\n      const classSet = table.chainClassSet[inputClass];\n\n      // If the class set is null there's nothing to do with this table.\n      if (!classSet) {\n        continue;\n      }\n\n      for (const [subIndex, subTable] of classSet.entries()) {\n        const result: ILookupTree = {\n          individual: {},\n          range: []\n        };\n\n        let currentEntries: IEntryMeta[] = getInputTree(\n          result,\n          subTable.lookupRecords,\n          lookups,\n          0,\n          glyphId\n        ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] }));\n\n        for (const [index, classNum] of subTable.input.entries()) {\n          currentEntries = processInputPosition(\n            listClassGlyphs(table.inputClassDef, classNum),\n            index + 1,\n            currentEntries,\n            subTable.lookupRecords,\n            lookups\n          );\n        }\n\n        for (const classNum of subTable.lookahead) {\n          currentEntries = processLookaheadPosition(\n            listClassGlyphs(table.lookaheadClassDef, classNum),\n            currentEntries\n          );\n        }\n\n        for (const classNum of subTable.backtrack) {\n          currentEntries = processBacktrackPosition(\n            listClassGlyphs(table.backtrackClassDef, classNum),\n            currentEntries\n          );\n        }\n\n        // When we get to the end, all of the entries we've accumulated\n        // should have a lookup defined\n        for (const { entry, substitutions } of currentEntries) {\n          entry.lookup = {\n            substitutions,\n            index: tableIndex,\n            subIndex,\n            length: subTable.input.length + 1,\n            contextRange: [\n              -1 * subTable.backtrack.length,\n              1 + subTable.input.length + subTable.lookahead.length\n            ]\n          };\n        }\n\n        results.push(result);\n      }\n    }\n  }\n\n  return mergeTrees(results);\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/6-3.ts",
    "content": "import { ChainingContextualSubstitutionTable, Lookup } from '../tables';\nimport { ILookupTree } from '../types';\n\nimport { listGlyphsByIndex } from './coverage';\nimport { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, IEntryMeta } from './helper';\n\n/**\n * Build lookup tree for GSUB lookup table 6, format 3.\n * https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#63-chaining-context-substitution-format-3-coverage-based-glyph-contexts\n *\n * @param table JSON representation of the table\n * @param lookups List of lookup tables\n * @param tableIndex Index of this table in the overall lookup\n */\nexport default function buildTree(table: ChainingContextualSubstitutionTable.IFormat3, lookups: Lookup[], tableIndex: number): ILookupTree {\n  const result: ILookupTree = {\n    individual: {},\n    range: []\n  };\n\n  const firstGlyphs = listGlyphsByIndex(table.inputCoverage[0]);\n\n  for (const { glyphId } of firstGlyphs) {\n    let currentEntries: IEntryMeta[] = getInputTree(\n      result,\n      table.lookupRecords,\n      lookups,\n      0,\n      glyphId\n    ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] }));\n\n    for (const [index, coverage] of table.inputCoverage.slice(1).entries()) {\n      currentEntries = processInputPosition(\n        listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),\n        index + 1,\n        currentEntries,\n        table.lookupRecords,\n        lookups\n      );\n    }\n\n    for (const coverage of table.lookaheadCoverage) {\n      currentEntries = processLookaheadPosition(\n        listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),\n        currentEntries\n      );\n    }\n\n    for (const coverage of table.backtrackCoverage) {\n      currentEntries = processBacktrackPosition(\n        listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),\n        currentEntries\n      );\n    }\n\n    // When we get to the end, all of the entries we've accumulated\n    // should have a lookup defined\n    for (const { entry, substitutions } of currentEntries) {\n      entry.lookup = {\n        substitutions,\n        index: tableIndex,\n        subIndex: 0,\n        length: table.inputCoverage.length,\n        contextRange: [\n          -1 * table.backtrackCoverage.length,\n          table.inputCoverage.length + table.lookaheadCoverage.length\n        ]\n      };\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/8-1.ts",
    "content": "import { IReverseChainingContextualSingleSubstitutionTable } from '../tables';\nimport { ILookupTree, ILookupTreeEntry } from '../types';\n\nimport { listGlyphsByIndex } from './coverage';\nimport { processLookaheadPosition, processBacktrackPosition, IEntryMeta } from './helper';\n\n/**\n * Build lookup tree for GSUB lookup table 8, format 1.\n * https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#81-reverse-chaining-contextual-single-substitution-format-1-coverage-based-glyph-contexts\n *\n * @param table JSON representation of the table\n * @param tableIndex Index of this table in the overall lookup\n */\nexport default function buildTree(table: IReverseChainingContextualSingleSubstitutionTable, tableIndex: number): ILookupTree {\n  const result: ILookupTree = {\n    individual: {},\n    range: []\n  };\n\n  const glyphs = listGlyphsByIndex(table.coverage);\n\n  for (const { glyphId, index } of glyphs) {\n    const initialEntry: ILookupTreeEntry = {};\n    if (Array.isArray(glyphId)) {\n      result.range.push({\n        entry: initialEntry,\n        range: glyphId\n      });\n    } else {\n      result.individual[glyphId] = initialEntry;\n    }\n\n    let currentEntries: IEntryMeta[] = [{\n      entry: initialEntry,\n      substitutions: [table.substitutes[index]]\n    }];\n\n    // We walk forward, then backward\n    for (const coverage of table.lookaheadCoverage) {\n      currentEntries = processLookaheadPosition(\n        listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),\n        currentEntries\n      );\n    }\n\n    for (const coverage of table.backtrackCoverage) {\n      currentEntries = processBacktrackPosition(\n        listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),\n        currentEntries\n      );\n    }\n\n    // When we get to the end, insert the lookup information\n    for (const { entry, substitutions } of currentEntries) {\n      entry.lookup = {\n        substitutions,\n        index: tableIndex,\n        subIndex: 0,\n        length: 1,\n        contextRange: [\n          -1 * table.backtrackCoverage.length,\n          1 + table.lookaheadCoverage.length\n        ]\n      };\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/classDef.ts",
    "content": "import { ClassDefTable } from '../tables';\n\n/**\n * Get the number of the class to which the glyph belongs, or null if it doesn't\n * belong to any of them.\n *\n * @param table JSON representation of the class def table\n * @param glyphId Index of the glyph to look for\n */\nexport default function getGlyphClass(table: ClassDefTable, glyphId: number | [number, number]): Map<number | [number, number], number | null> {\n  switch (table.format) {\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2\n    case 2:\n      if (Array.isArray(glyphId)) {\n        return getRangeGlyphClass(table, glyphId);\n      }\n      return new Map([[\n        glyphId,\n        getIndividualGlyphClass(table, glyphId)\n      ]]);\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-1\n    default:\n      return new Map([[glyphId, null]]);\n  }\n}\n\nfunction getRangeGlyphClass(table: ClassDefTable.IFormat2, glyphId: [number, number]): Map<number | [number, number], number | null> {\n  const classStart: number = glyphId[0];\n  const currentClass: number | null = getIndividualGlyphClass(table, classStart);\n  let search: number = glyphId[0] + 1;\n\n  const result = new Map<[number, number] | number, number | null>();\n\n  while (search < glyphId[1]) {\n    const clazz = getIndividualGlyphClass(table, search);\n    if (clazz !== currentClass) {\n      if (search - classStart <= 1) {\n        result.set(classStart, currentClass);\n      } else {\n        result.set([classStart, search], currentClass);\n      }\n    }\n    search++;\n  }\n\n  if (search - classStart <= 1) {\n    result.set(classStart, currentClass);\n  } else {\n    result.set([classStart, search], currentClass);\n  }\n\n  return result;\n}\n\nfunction getIndividualGlyphClass(table: ClassDefTable.IFormat2, glyphId: number): number | null {\n  for (const range of table.ranges) {\n    if (range.start <= glyphId && range.end >= glyphId) {\n      return range.classId;\n    }\n  }\n\n  return null;\n}\n\nexport function listClassGlyphs(table: ClassDefTable, index: number): (number | [number, number])[] {\n  switch (table.format) {\n    case 2:\n      const results: (number | [number, number])[] = [];\n      for (const range of table.ranges) {\n        if (range.classId !== index) {\n          continue;\n        }\n\n        if (range.end === range.start) {\n          results.push(range.start);\n        } else {\n          results.push([range.start, range.end + 1]);\n        }\n      }\n      return results;\n    default:\n      return [];\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/coverage.ts",
    "content": "import { CoverageTable } from '../tables';\n\n/**\n * Get the index of the given glyph in the coverage table, or null if it is not\n * present in the table.\n *\n * @param table JSON representation of the coverage table\n * @param glyphId Index of the glyph to look for\n */\nexport default function getCoverageGlyphIndex(table: CoverageTable, glyphId: number): number | null {\n  switch (table.format) {\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1\n    case 1:\n      const index = table.glyphs.indexOf(glyphId);\n      return index !== -1\n        ? index\n        : null;\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2\n    case 2:\n      const range = table.ranges\n        .find(range => range.start <= glyphId && range.end >= glyphId);\n      return range\n        ? range.index\n        : null;\n  }\n}\n\nexport function listGlyphsByIndex(table: CoverageTable): { glyphId: number | [number, number], index: number }[] {\n  switch (table.format) {\n    case 1:\n      return table.glyphs.map((glyphId, index) => ({ glyphId, index }));\n    case 2:\n      const results: { glyphId: number | [number, number], index: number }[] = [];\n      for (const [index, range] of table.ranges.entries()) {\n        if (range.end === range.start) {\n          results.push({ glyphId: range.start, index });\n        } else {\n          results.push({ glyphId: [range.start, range.end + 1], index });\n        }\n      }\n      return results;\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/helper.ts",
    "content": "import { ILookupTreeEntry, ILookupTree } from '../types';\nimport { ISubstitutionLookupRecord, Lookup } from '../tables';\n\nimport { getIndividualSubstitutionGlyph, getRangeSubstitutionGlyphs } from './substitution';\n\nexport interface IEntryMeta {\n  entry: ILookupTreeEntry;\n  substitutions: (number | null)[];\n}\n\nexport function processInputPosition(\n  glyphs: (number | [number, number])[],\n  position: number,\n  currentEntries: IEntryMeta[],\n  lookupRecords: ISubstitutionLookupRecord[],\n  lookups: Lookup[]\n): IEntryMeta[] {\n  const nextEntries: IEntryMeta[] = [];\n  for (const currentEntry of currentEntries) {\n    currentEntry.entry.forward = {\n      individual: {},\n      range: []\n    };\n    for (const glyph of glyphs) {\n      nextEntries.push(...getInputTree(\n        currentEntry.entry.forward,\n        lookupRecords,\n        lookups,\n        position,\n        glyph\n      ).map(({ entry, substitution }) => ({\n        entry,\n        substitutions: [...currentEntry.substitutions, substitution]\n      })));\n    }\n  }\n\n  return nextEntries;\n}\n\nexport function processLookaheadPosition(\n  glyphs: (number | [number, number])[],\n  currentEntries: IEntryMeta[]\n): IEntryMeta[] {\n  const nextEntries: IEntryMeta[] = [];\n  const processedEntries = new Set<ILookupTreeEntry>();\n\n  for (const currentEntry of currentEntries) {\n    // Skip if we've already processed this entry object\n    if (processedEntries.has(currentEntry.entry)) {\n      continue;\n    }\n    processedEntries.add(currentEntry.entry);\n\n    currentEntry.entry.forward ??= {\n      individual: {},\n      range: []\n    };\n\n    // All glyphs at this position share ONE entry - lookahead just needs to match,\n    // all paths lead to the same result\n    const sharedEntry: ILookupTreeEntry = {};\n\n    for (const glyph of glyphs) {\n      if (Array.isArray(glyph)) {\n        currentEntry.entry.forward.range.push({\n          entry: sharedEntry,\n          range: glyph\n        });\n      } else {\n        currentEntry.entry.forward.individual[glyph] = sharedEntry;\n      }\n    }\n\n    nextEntries.push({\n      entry: sharedEntry,\n      substitutions: currentEntry.substitutions\n    });\n  }\n\n  return nextEntries;\n}\n\nexport function processBacktrackPosition(\n  glyphs: (number | [number, number])[],\n  currentEntries: IEntryMeta[]\n): IEntryMeta[] {\n  const nextEntries: IEntryMeta[] = [];\n  const processedEntries = new Set<ILookupTreeEntry>();\n\n  for (const currentEntry of currentEntries) {\n    // Skip if we've already processed this entry object\n    if (processedEntries.has(currentEntry.entry)) {\n      continue;\n    }\n    processedEntries.add(currentEntry.entry);\n\n    currentEntry.entry.reverse ??= {\n      individual: {},\n      range: []\n    };\n\n    // All glyphs at this position share ONE entry - backtrack just needs to match,\n    // all paths lead to the same result\n    const sharedEntry: ILookupTreeEntry = {};\n\n    for (const glyph of glyphs) {\n      if (Array.isArray(glyph)) {\n        currentEntry.entry.reverse.range.push({\n          entry: sharedEntry,\n          range: glyph\n        });\n      } else {\n        currentEntry.entry.reverse.individual[glyph] = sharedEntry;\n      }\n    }\n\n    nextEntries.push({\n      entry: sharedEntry,\n      substitutions: currentEntry.substitutions\n    });\n  }\n\n  return nextEntries;\n}\n\nexport function getInputTree(tree: ILookupTree, substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], inputIndex: number, glyphId: number | [number, number]): { entry: ILookupTreeEntry, substitution: number | null }[] {\n  const result: { entry: ILookupTreeEntry, substitution: number | null }[] = [];\n  if (!Array.isArray(glyphId)) {\n    tree.individual[glyphId] = {};\n    result.push({\n      entry: tree.individual[glyphId],\n      substitution: getSubstitutionAtPosition(substitutions, lookups, inputIndex, glyphId)\n    });\n  } else {\n    const subs = getSubstitutionAtPositionRange(substitutions, lookups, inputIndex, glyphId);\n    for (const [range, substitution] of subs) {\n      const entry: ILookupTreeEntry = {};\n      if (Array.isArray(range)) {\n        tree.range.push({ range, entry });\n      } else {\n        tree.individual[range] = {};\n      }\n      result.push({ entry, substitution });\n    }\n  }\n\n  return result;\n}\n\nfunction getSubstitutionAtPositionRange(substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], index: number, range: [number, number]): Map<number | [number, number], number | null> {\n  for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) {\n    for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {\n      const sub = getRangeSubstitutionGlyphs(\n        substitutionTable,\n        range\n      );\n\n      if (!Array.from(sub.values()).every(val => val !== null)) {\n        return sub;\n      }\n    }\n  }\n\n  return new Map([[range, null]]);\n}\n\nfunction getSubstitutionAtPosition(substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], index: number, glyphId: number): number | null {\n  for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) {\n    for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {\n      const sub = getIndividualSubstitutionGlyph(\n        substitutionTable,\n        glyphId\n      );\n\n      if (sub !== null) {\n        return sub;\n      }\n    }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/processors/substitution.ts",
    "content": "import { SubstitutionTable } from '../tables';\n\nimport getCoverageGlyphIndex from './coverage';\n\n/**\n * Get the substitution glyph for the givne glyph, or null if the glyph was not\n * found in the table.\n *\n * @param table JSON representation of the substitution table\n * @param glyphId The index of the glpyh to find substitutions for\n */\nexport function getRangeSubstitutionGlyphs(table: SubstitutionTable, glyphId: [number, number]): Map<[number, number] | number, number | null> {\n  const replacementStart: number = glyphId[0];\n  const currentReplacement: number | null = getIndividualSubstitutionGlyph(table, replacementStart);\n  let search: number = glyphId[0] + 1;\n\n  const result = new Map<[number, number] | number, number | null>();\n\n  while (search < glyphId[1]) {\n    const sub = getIndividualSubstitutionGlyph(table, search);\n    if (sub !== currentReplacement) {\n      if (search - replacementStart <= 1) {\n        result.set(replacementStart, currentReplacement);\n      } else {\n        result.set([replacementStart, search], currentReplacement);\n      }\n    }\n\n    search++;\n  }\n\n  if (search - replacementStart <= 1) {\n    result.set(replacementStart, currentReplacement);\n  } else {\n    result.set([replacementStart, search], currentReplacement);\n  }\n\n  return result;\n}\n\nexport function getIndividualSubstitutionGlyph(table: SubstitutionTable, glyphId: number): number | null {\n  const coverageIndex = getCoverageGlyphIndex(table.coverage, glyphId);\n\n  // istanbul ignore next - invalid font\n  if (coverageIndex === null) {\n    return null;\n  }\n\n  switch (table.substFormat) {\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1\n    case 1:\n      // TODO: determine if there's a rhyme or reason to the 16-bit\n      // wraparound and if it can ever be a different number\n      return (glyphId + table.deltaGlyphId) % (2 ** 16);\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#12-single-substitution-format-2\n    case 2:\n      return table.substitute[coverageIndex] ?? null;\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/tables.ts",
    "content": "export type SubstitutionTable = SubstitutionTable.IFormat1 | SubstitutionTable.IFormat2;\nexport namespace SubstitutionTable {\n  export interface IFormat1 {\n    substFormat: 1;\n    coverage: CoverageTable;\n    deltaGlyphId: number;\n  }\n\n  export interface IFormat2 {\n    substFormat: 2;\n    coverage: CoverageTable;\n    substitute: number[];\n  }\n}\n\nexport type CoverageTable = CoverageTable.IFormat1 | CoverageTable.IFormat2;\nexport namespace CoverageTable {\n  export interface IFormat1 {\n    format: 1;\n    glyphs: number[];\n  }\n\n  export interface IFormat2 {\n    format: 2;\n    ranges: {\n      start: number;\n      end: number;\n      index: number;\n    }[];\n  }\n}\n\nexport type ChainingContextualSubstitutionTable = ChainingContextualSubstitutionTable.IFormat1 |\n  ChainingContextualSubstitutionTable.IFormat2 | ChainingContextualSubstitutionTable.IFormat3;\nexport namespace ChainingContextualSubstitutionTable {\n  export interface IFormat1 {\n    substFormat: 1;\n    coverage: CoverageTable;\n    chainRuleSets: ChainSubRuleTable[][];\n  }\n\n  export interface IFormat2 {\n    substFormat: 2;\n    coverage: CoverageTable;\n    backtrackClassDef: ClassDefTable;\n    inputClassDef: ClassDefTable;\n    lookaheadClassDef: ClassDefTable;\n    chainClassSet: (null | IChainSubClassRuleTable[])[];\n  }\n\n  export interface IFormat3 {\n    substFormat: 3;\n    backtrackCoverage: CoverageTable[];\n    inputCoverage: CoverageTable[];\n    lookaheadCoverage: CoverageTable[];\n    lookupRecords: ISubstitutionLookupRecord[];\n  }\n}\n\nexport interface IReverseChainingContextualSingleSubstitutionTable {\n  substFormat: 1;\n  coverage: CoverageTable;\n  backtrackCoverage: CoverageTable[];\n  lookaheadCoverage: CoverageTable[];\n  substitutes: number[];\n}\n\nexport type ClassDefTable = ClassDefTable.IFormat2;\nexport namespace ClassDefTable {\n  export interface IFormat2 {\n    format: 2;\n    ranges: {\n      start: number;\n      end: number;\n      classId: number;\n    }[];\n  }\n}\n\nexport interface ISubstitutionLookupRecord {\n  sequenceIndex: number;\n  lookupListIndex: number;\n}\n\nexport type ChainSubRuleTable = IChainSubClassRuleTable;\nexport interface IChainSubClassRuleTable {\n  backtrack: number[];\n  input: number[];\n  lookahead: number[];\n  lookupRecords: ISubstitutionLookupRecord[];\n}\n\nexport type Lookup = Lookup.IType1 | Lookup.IType6 | Lookup.IType8;\nexport namespace Lookup {\n  export interface IType1 {\n    lookupType: 1;\n    lookupFlag: number;\n    subtables: SubstitutionTable[];\n  }\n\n  export interface IType6 {\n    lookupType: 6;\n    lookupFlag: number;\n    subtables: ChainingContextualSubstitutionTable[];\n  }\n\n  export interface IType8 {\n    lookupType: 8;\n    lookupFlag: number;\n    subtables: IReverseChainingContextualSingleSubstitutionTable[];\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/types.ts",
    "content": "export interface ISubstitutionResult {\n  index: number;\n  contextRange: [number, number];\n}\n\n/**\n * Information about ligatures found in a sequence of text\n */\nexport interface ILigatureData {\n  /**\n   * The list of font glyphs in the input text.\n   */\n  inputGlyphs: number[];\n\n  /**\n   * The list of font glyphs after performing replacements for font ligatures.\n   */\n  outputGlyphs: number[];\n\n  /**\n   * Sorted array of ranges that must be rendered together to produce the\n   * ligatures in the output sequence. The ranges are inclusive on the left and\n   * exclusive on the right.\n   */\n  contextRanges: [number, number][];\n}\n\nexport interface IFont {\n  /**\n   * Scans the provided text for font ligatures, returning an object with\n   * metadata about the text and any ligatures found.\n   *\n   * @param text String to search for ligatures\n   */\n  findLigatures(text: string): ILigatureData;\n\n  /**\n   * Scans the provided text for font ligatures, returning an array of ranges\n   * where ligatures are located.\n   *\n   * @param text String to search for ligatures\n   */\n  findLigatureRanges(text: string): [number, number][];\n}\n\nexport interface IOptions {\n  /**\n   * Optional size of previous results to store, measured in total number of\n   * characters from input strings. Defaults to no cache (0)\n   */\n  cacheSize?: number;\n}\n\nexport interface ILookupTree {\n  individual: {\n    [glyphId: string]: ILookupTreeEntry;\n  };\n  range: {\n    range: [number, number];\n    entry: ILookupTreeEntry;\n  }[];\n}\n\nexport interface ILookupTreeEntry {\n  lookup?: ILookupResult;\n  forward?: ILookupTree;\n  reverse?: ILookupTree;\n}\n\nexport interface ILookupResult {\n  substitutions: (number | null)[];\n  length: number;\n  index: number;\n  subIndex: number;\n  contextRange: [number, number];\n}\n\nexport interface IFlattenedLookupTree {\n  [glyphId: string]: IFlattenedLookupTreeEntry;\n}\n\nexport interface IFlattenedLookupTreeEntry {\n  lookup?: ILookupResult;\n  forward?: IFlattenedLookupTree;\n  reverse?: IFlattenedLookupTree;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/fontLigatures/walk.ts",
    "content": "import { IFlattenedLookupTree, ILookupResult } from './types';\n\nexport default function walkTree(tree: IFlattenedLookupTree, sequence: number[], startIndex: number, index: number): ILookupResult | undefined {\n  const glyphId = sequence[index];\n  const subtree = tree[glyphId];\n  if (!subtree) {\n    return undefined;\n  }\n\n  let lookup = subtree.lookup;\n  if (subtree.reverse) {\n    const reverseLookup = walkReverse(subtree.reverse, sequence, startIndex);\n\n    if (\n      (!lookup && reverseLookup) ||\n      (\n        reverseLookup && lookup && (\n          lookup.index > reverseLookup.index ||\n          (lookup.index === reverseLookup.index && lookup.subIndex > reverseLookup.subIndex)\n        )\n      )\n    ) {\n      lookup = reverseLookup;\n    }\n  }\n\n  if (++index >= sequence.length || !subtree.forward) {\n    return lookup;\n  }\n\n  const forwardLookup = walkTree(subtree.forward, sequence, startIndex, index);\n\n  if (\n    (!lookup && forwardLookup) ||\n    (\n      forwardLookup && lookup && (\n        lookup.index > forwardLookup.index ||\n        (lookup.index === forwardLookup.index && lookup.subIndex > forwardLookup.subIndex)\n      )\n    )\n  ) {\n    lookup = forwardLookup;\n  }\n\n  return lookup;\n}\n\nfunction walkReverse(tree: IFlattenedLookupTree, sequence: number[], index: number): ILookupResult | undefined {\n  let subtree = tree[sequence[--index]];\n  let lookup: ILookupResult | undefined = subtree && subtree.lookup;\n  while (subtree) {\n    if (\n      (!lookup && subtree.lookup) ||\n      (subtree.lookup && lookup && lookup.index > subtree.lookup.index)\n    ) {\n      lookup = subtree.lookup;\n    }\n\n    if (--index < 0 || !subtree.reverse) {\n      break;\n    }\n\n    subtree = subtree.reverse[sequence[index]];\n  }\n\n  return lookup;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/index.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal } from '@xterm/xterm';\nimport { Font } from './fontLigatures/index';\n\nimport load from './font';\n\nconst enum LoadingState {\n  UNLOADED,\n  LOADING,\n  LOADED,\n  FAILED\n}\n\n// Caches 100K characters worth of ligatures. In practice this works out to\n// about 650 KB worth of cache, when a moderate number of ligatures are present.\nconst CACHE_SIZE = 100000;\n\n/**\n * Enable ligature support for the provided Terminal instance. To function\n * properly, this must be called after `open()` is called on the therminal. If\n * the font currently in use supports ligatures, the terminal will automatically\n * start to render them.\n * @param term Terminal instance from xterm.js\n */\nexport function enableLigatures(term: Terminal, fallbackLigatures: string[] = []): number {\n  let currentFontName: string | undefined = undefined;\n  let font: Font | undefined = undefined;\n  let loadingState: LoadingState = LoadingState.UNLOADED;\n  let loadError: unknown = undefined;\n\n  return term.registerCharacterJoiner((text: string): [number, number][] => {\n    // If the font hasn't been loaded yet, load it and return an empty result\n    const termFont = term.options.fontFamily;\n    if (\n      termFont &&\n      (loadingState === LoadingState.UNLOADED || currentFontName !== termFont)\n    ) {\n      font = undefined;\n      loadingState = LoadingState.LOADING;\n      currentFontName = termFont;\n      const currentCallFontName = currentFontName;\n\n      load(currentCallFontName, CACHE_SIZE)\n        .then(f => {\n          // Another request may have come in while we were waiting, so make\n          // sure our font is still vaild.\n          if (currentCallFontName === term.options.fontFamily) {\n            loadingState = LoadingState.LOADED;\n            font = f;\n\n            // Only refresh things if we actually found a font\n            if (f) {\n              term.refresh(0, term.rows - 1);\n            }\n          }\n        })\n        .catch(e => {\n          // Another request may have come in while we were waiting, so make\n          // sure our font is still vaild.\n          if (currentCallFontName === term.options.fontFamily) {\n            loadingState = LoadingState.FAILED;\n            if (term.options.logLevel === 'debug') {\n              console.debug(loadError, new Error('Failure while loading font'));\n            }\n            font = undefined;\n            loadError = e;\n          }\n        });\n    }\n\n    if (font && loadingState === LoadingState.LOADED) {\n      // We clone the entries to avoid the internal cache of the ligature finder\n      // getting messed up.\n      return font.findLigatureRanges(text).map<[number, number]>(\n        range => [range[0], range[1]]\n      );\n    }\n\n    return getFallbackRanges(text, fallbackLigatures);\n  });\n}\n\nfunction getFallbackRanges(text: string, fallbackLigatures: string[]): [number, number][] {\n  const ranges: [number, number][] = [];\n  for (let i = 0; i < text.length; i++) {\n    for (let j = 0; j < fallbackLigatures.length; j++) {\n      if (text.startsWith(fallbackLigatures[j], i)) {\n        ranges.push([i, i + fallbackLigatures[j].length]);\n        i += fallbackLigatures[j].length - 1;\n        break;\n      }\n    }\n  }\n  return ranges;\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/parse.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport parse from './parse';\n\n// TODO: integrate tests from http://test.csswg.org/suites/css-fonts-4_dev/nightly-unstable/\ndescribe('addon-ligatures - parse', () => {\n  it('parses individual families', () => {\n    assert.deepEqual(parse('monospace'), ['monospace']);\n  });\n\n  it('parses multiple families', () => {\n    assert.deepEqual(parse('Arial, Verdana, serif'), ['Arial', 'Verdana', 'serif']);\n  });\n\n  it('parses quoted families', () => {\n    assert.deepEqual(parse('\"Times New Roman\", serif'), ['Times New Roman', 'serif']);\n  });\n\n  it('parses single quoted families', () => {\n    assert.deepEqual(parse('\\'Times New Roman\\', serif'), ['Times New Roman', 'serif']);\n  });\n\n  it('parses families with spaces in their names', () => {\n    assert.deepEqual(parse('Times New Roman, serif'), ['Times New Roman', 'serif']);\n  });\n\n  it('collapses multiple spaces together in identifiers', () => {\n    assert.deepEqual(parse('Times   New Roman, serif'), ['Times New Roman', 'serif']);\n  });\n\n  it('does not collapse multiple spaces together in quoted strings', () => {\n    assert.deepEqual(parse('\"Times   New Roman\", serif'), ['Times   New Roman', 'serif']);\n  });\n\n  it('handles escaped characters in strings', () => {\n    assert.deepEqual(parse('\"quote \\\\\" slash \\\\\\\\ slashquote \\\\\\\\\\\\\"\", serif'), ['quote \" slash \\\\ slashquote \\\\\"', 'serif']);\n  });\n\n  it('fails if a family has an unterminated string', () => {\n    assert.throws(() => parse('\"Unterminated, serif'));\n  });\n\n  it('handles unicode escape sequences', () => {\n    assert.deepEqual(parse('\"space\\\\20 between\", serif'), ['space between', 'serif']);\n  });\n\n  it('swallows only the first space after a unicode escape', () => {\n    assert.deepEqual(parse('\"two-space\\\\20  between\", serif'), ['two-space  between', 'serif']);\n  });\n\n  it('automatically ends the unicode escape after six digits', () => {\n    assert.deepEqual(parse('space\\\\000020between, serif'), ['space between', 'serif']);\n  });\n\n  it('handles unicode escapes at the end of the family', () => {\n    assert.deepEqual(parse('endswithbrace \\\\7b, serif'), ['endswithbrace {', 'serif']);\n  });\n\n  it('handles unicode escapes at the end of the input', () => {\n    assert.deepEqual(parse('endswithbrace \\\\7b'), ['endswithbrace {']);\n  });\n\n  it('handles other escaped characters in identifiers', () => {\n    assert.deepEqual(parse('has\\\\,comma'), ['has,comma']);\n  });\n\n  it('swallows escaped newlines in strings', () => {\n    assert.deepEqual(parse('\"multi \\\\\\nline\", serif'), ['multi line', 'serif']);\n  });\n});\n"
  },
  {
    "path": "addons/addon-ligatures/src/parse.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\ninterface IParseContext {\n  input: string;\n  offset: number;\n}\n\n/**\n * Parses a CSS font family value, returning the component font families\n * contained within.\n *\n * @param family The CSS font family input string to parse\n */\nexport default function parse(family: string): string[] {\n  if (typeof family !== 'string') {\n    throw new Error('Font family must be a string');\n  }\n\n  const context: IParseContext = {\n    input: family,\n    offset: 0\n  };\n\n  const families = [];\n  let currentFamily = '';\n\n  // Work through the input character by character until there are none left.\n  // This lexing and parsing in one pass.\n  while (context.offset < context.input.length) {\n    const char = context.input[context.offset++];\n    switch (char) {\n      // String\n      case '\\'':\n      case '\"':\n        currentFamily += parseString(context, char);\n        break;\n      // End of family\n      case ',':\n        families.push(currentFamily);\n        currentFamily = '';\n        break;\n      default:\n        // Identifiers (whitespace between families is swallowed)\n        if (!/\\s/.test(char)) {\n          context.offset--;\n          currentFamily += parseIdentifier(context);\n          families.push(currentFamily);\n          currentFamily = '';\n        }\n    }\n  }\n\n  return families;\n}\n\n/**\n * Parse a CSS string.\n *\n * @param context Parsing input and offset\n * @param quoteChar The quote character for the string (' or \")\n */\nfunction parseString(context: IParseContext, quoteChar: '\\'' | '\"'): string {\n  let str = '';\n  let escaped = false;\n  while (context.offset < context.input.length) {\n    const char = context.input[context.offset++];\n    if (escaped) {\n      if (/[\\dA-Fa-f]/.test(char)) {\n        // Unicode escape\n        context.offset--;\n        str += parseUnicode(context);\n      } else if (char !== '\\n') {\n        // Newlines are ignored if escaped. Other characters are used as is.\n        str += char;\n      }\n      escaped = false;\n    } else {\n      switch (char) {\n        // Terminated quote\n        case quoteChar:\n          return str;\n        // Begin escape\n        case '\\\\':\n          escaped = true;\n          break;\n        // Add character to string\n        default:\n          str += char;\n      }\n    }\n  }\n\n  throw new Error('Unterminated string');\n}\n\n/**\n * Parse a CSS custom identifier.\n *\n * @param context Parsing input and offset\n */\nfunction parseIdentifier(context: IParseContext): string {\n  let str = '';\n  let escaped = false;\n  while (context.offset < context.input.length) {\n    const char = context.input[context.offset++];\n    if (escaped) {\n      if (/[\\dA-Fa-f]/.test(char)) {\n        // Unicode escape\n        context.offset--;\n        str += parseUnicode(context);\n      } else {\n        // Everything else is used as is\n        str += char;\n      }\n      escaped = false;\n    } else {\n      switch (char) {\n        // Begin escape\n        case '\\\\':\n          escaped = true;\n          break;\n        // Terminate identifier\n        case ',':\n          return str;\n        default:\n          if (/\\s/.test(char)) {\n            // Whitespace is collapsed into a single space within an identifier\n            if (!str.endsWith(' ')) {\n              str += ' ';\n            }\n          } else {\n            // Add other characters directly\n            str += char;\n          }\n      }\n    }\n  }\n\n  return str;\n}\n\n/**\n * Parse a CSS unicode escape.\n *\n * @param context Parsing input and offset\n */\nfunction parseUnicode(context: IParseContext): string {\n  let str = '';\n  while (context.offset < context.input.length) {\n    const char = context.input[context.offset++];\n    if (/\\s/.test(char)) {\n      // The first whitespace character after a unicode escape indicates the end\n      // of the escape and is swallowed.\n      return unicodeToString(str);\n    }\n    if (str.length >= 6 || !/[\\dA-Fa-f]/.test(char)) {\n      // If the next character is not a valid hex digit or we have reached the\n      // maximum of 6 digits in the escape, terminate the escape.\n      context.offset--;\n      return unicodeToString(str);\n    }\n\n    // Otherwise, just add it to the escape\n    str += char;\n  }\n\n  return unicodeToString(str);\n}\n\n/**\n * Convert a unicode code point from a hex string to a utf8 string.\n *\n * @param codePoint Unicode code point represented as a hex string\n */\nfunction unicodeToString(codePoint: string): string {\n  return String.fromCodePoint(parseInt(codePoint, 16));\n}\n"
  },
  {
    "path": "addons/addon-ligatures/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"sourceMap\": true,\n    \"outDir\": \"../out\",\n    \"rootDir\": \".\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"preserveWatchOutput\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\",\n      // HACK: src shouldn't use node types but it's needed for index.test.ts\n      \"../../../node_modules/@types/node\"\n    ],\n    \"paths\": {\n      \"@xterm/addon-ligatures\" : [\n        \"../typings/addon-ligatures.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/addon-ligatures/test/LigaturesAddon.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as path from 'path';\nimport { assert } from 'chai';\n\n// Use require to get a mutable module object (ESM imports create read-only bindings)\nconst fontFinder = require('font-finder');\nconst ligatureSupport = require('../out-esbuild/index');\n\nconst originalList = fontFinder.list;\n\ndescribe('LigaturesAddon', () => {\n  let onRefresh: { called: boolean, callCount: number, (...args: any[]): void };\n  let term: MockTerminal;\n\n  const input = 'a -> b www c';\n\n  before(() => {\n    fontFinder.list = () => Promise.resolve({\n      'Fira Code': [{\n        path: path.join(__dirname, '../fonts/firaCode.otf'),\n        style: fontFinder.Style.Regular,\n        type: fontFinder.Type.Monospace,\n        weight: 400\n      }],\n      'Iosevka': [{\n        path: path.join(__dirname, '../fonts/iosevka.ttf'),\n        style: fontFinder.Style.Regular,\n        type: fontFinder.Type.Monospace,\n        weight: 400\n      }],\n      'Nonexistant Font': [{\n        path: path.join(__dirname, '../fonts/nonexistant.ttf'),\n        style: fontFinder.Style.Regular,\n        type: fontFinder.Type.Monospace,\n        weight: 400\n      }]\n    });\n  });\n\n  after(() => {\n    fontFinder.list = originalList;\n  });\n\n  beforeEach(() => {\n    onRefresh = Object.assign((..._args: any[]) => { onRefresh.called = true; onRefresh.callCount++; }, { called: false, callCount: 0 });\n    term = new MockTerminal(onRefresh);\n    ligatureSupport.enableLigatures(term as any);\n  });\n\n  it('registers itself correctly', () => {\n    const term = new MockTerminal(() => {});\n    assert.isUndefined(term.joiner);\n    ligatureSupport.enableLigatures(term as any);\n    assert.isFunction(term.joiner);\n  });\n\n  it('registers itself correctly when called directly', () => {\n    const term = new MockTerminal(() => {});\n    assert.isUndefined(term.joiner);\n    ligatureSupport.enableLigatures(term as any);\n    assert.isFunction(term.joiner);\n  });\n\n  it('returns an empty set of ranges on the first call while the font is loading', () => {\n    assert.deepEqual(term.joiner!(input), []);\n  });\n\n  it('fails if it finds but cannot load the font', async () => {\n    term.options.fontFamily = 'Nonexistant Font, monospace';\n    assert.deepEqual(term.joiner!(input), []);\n    await delay(500);\n    assert.strictEqual(onRefresh.callCount, 0);\n  });\n\n  it('returns nothing if the font is not present on the system', async () => {\n    term.options.fontFamily = 'notinstalled';\n    assert.deepEqual(term.joiner!(input), []);\n    await delay(500);\n    assert.strictEqual(onRefresh.callCount, 0);\n    assert.deepEqual(term.joiner!(input), []);\n  });\n\n  it('returns nothing if no specific font is specified', async () => {\n    term.options.fontFamily = 'monospace';\n    assert.deepEqual(term.joiner!(input), []);\n    await delay(500);\n    assert.strictEqual(onRefresh.callCount, 0);\n    assert.deepEqual(term.joiner!(input), []);\n  });\n\n  it('returns nothing if no fonts are provided', async () => {\n    term.options.fontFamily = '';\n    assert.deepEqual(term.joiner!(input), []);\n    await delay(500);\n    assert.strictEqual(onRefresh.callCount, 0);\n    assert.deepEqual(term.joiner!(input), []);\n  });\n\n  it('fails when given malformed inputs', async () => {\n    term.options.fontFamily = {} as any;\n    assert.deepEqual(term.joiner!(input), []);\n    await delay(500);\n    assert.strictEqual(onRefresh.callCount, 0);\n  });\n});\n\nclass MockTerminal {\n  private _options: { [name: string]: string | number } = {\n    fontFamily: 'Fira Code, monospace',\n    rows: 50\n  };\n  public joiner?: (text: string) => [number, number][];\n  public refresh: (start: number, end: number) => void;\n\n  constructor(onRefresh: (start: number, end: number) => void) {\n    this.refresh = onRefresh;\n  }\n\n  public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {\n    this.joiner = handler;\n    return 1;\n  }\n  public deregisterCharacterJoiner(id: number): void {\n    this.joiner = undefined;\n  }\n  public get options(): { [name: string]: string | number } { return this._options; }\n  public set options(options: { [name: string]: string | number }) {\n    for (const key in this._options) {\n      this._options[key] = options[key];\n    }\n  }\n}\n\nfunction delay(delayMs: number): Promise<void> {\n  return new Promise(resolve => setTimeout(resolve, delayMs));\n}\n"
  },
  {
    "path": "addons/addon-ligatures/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-esbuild-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"paths\": {\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"types\": [\n      \"../../../node_modules/@types/mocha\",\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/addon-ligatures/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-ligatures/typings/addon-ligatures.d.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This contains the type declarations for the @xterm/addon-ligatures library.\n * Note that some interfaces may differ between this file and the actual\n * implementation in src/, that's because this file declares the *public* API\n * which is intended to be stable and consumed by external programs.\n */\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-ligatures' {\n  /**\n   * An xterm.js addon that enables web links.\n   */\n  export class LigaturesAddon implements ITerminalAddon {\n    /**\n     * Creates a new ligatures addon.\n     *\n     * @param options Options for the ligatures addon.\n     */\n    constructor(options?: Partial<ILigatureOptions>);\n\n    /**\n     * Activates the addon. Note that if webgl is also being used, that addon\n     * should be reactivated after ligatures is activated in order to apply\n     * {@link ILigatureOptions.fontFeatureSettings} to the texture atlas.\n     *\n     *\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n  }\n\n  /**\n   * Options for the ligatures addon.\n   */\n  export interface ILigatureOptions {\n    /**\n     * Fallback ligatures to use when the font access API is either not\n     * supported by the browser or access is denied. The default set of\n     * ligatures is taken from Iosevka's default \"calt\" ligation set:\n     * https://typeof.net/Iosevka/\n     *\n     * ```\n     * <-- <--- <<- <- -> ->> --> --->\n     * <== <=== <<= <= => =>> ==> ===> >= >>=\n     * <-> <--> <---> <----> <=> <==> <===> <====> :: :::\n     * <~~ </ </> /> ~~> == != /= ~= <> === !== !===\n     * <: := *= *+ <* <*> *> <| <|> |> +* =* =: :>\n     * /* <close block comment> +++ <!-- <!---\n     */\n    fallbackLigatures: string[]\n\n    /**\n     * The CSS `font-feature-settings` value to use for enabling ligatures. This\n     * also supports font variants for example with a value like\n     * `\"calt\" on, \"ss03\"`.\n     *\n     * The default value is `\"calt\" on`.\n     */\n    fontFeatureSettings: string;\n  }\n}\n"
  },
  {
    "path": "addons/addon-ligatures/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'LigaturesAddon';\nconst mainFile = 'addon-ligatures.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production',\n  externals: {\n    'fs': 'commonjs fs',\n    'path': 'commonjs path',\n    'stream': 'commonjs stream',\n    'util': 'commonjs util'\n  },\n  resolve: {\n    // The ligature modules contains fallbacks for node environments, we never want to browserify them\n    fallback: {\n      fs: false,\n      os: false,\n      path: false,\n      stream: false,\n      util: false\n    }\n  }\n};\n"
  },
  {
    "path": "addons/addon-progress/.gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": "addons/addon-progress/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-progress/LICENSE",
    "content": "Copyright (c) 2024, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-progress/README.md",
    "content": "## @xterm/addon-progress\n\nAn xterm.js addon providing an interface for ConEmu's progress sequence.\nSee https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC for sequence details.\n\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-progress\n```\n\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { ProgressAddon, IProgressState } from '@xterm/addon-progress';\n\nconst terminal = new Terminal();\nconst progressAddon = new ProgressAddon();\nterminal.loadAddon(progressAddon);\nprogressAddon.onChange({state, value}: IProgressState) => {\n  // state: 0-4 integer (see below for meaning)\n  // value: 0-100 integer (percent value)\n  \n  // do your visualisation based on state/value here\n  ...\n});\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-progress/typings/addon-progress.d.ts) for more advanced usage.\n\n### Sequence\n\nThe sequence to set progress information has the following format:\n\n```plain\nESC ] 9 ; 4 ; <state> ; <progress value> BEL\n```\n\nwhere state is a decimal number in 0 to 4 and progress value is a decimal number in 0 to 100.\nThe states have the following meaning:\n\n- 0: Remove any progress indication. Also resets progress value to 0. A given progress value will be ignored.\n- 1: Normal state to set a progress value. The value should be in 0..100, greater values are clamped to 100.\n  If the value is omitted, it will be set to 0.\n- 2: Error state with an optional progress value. An omitted value will be set to 0,\n  which has a special meaning using the last active value.\n- 3: Actual progress is \"indeterminate\", any progress value will be ignored. Meant to be used to indicate\n  a running task without progress information (e.g. by a spinner). A previously set progress value\n  by any other state sequence will be left untouched.\n- 4: Pause or warning state with an optional progress value. An omitted value will be set to 0,\n  which has a special meaning using the last active value.\n\nThe addon resolves most of those semantic nuances and will provide these ready-to-go values:\n- For the remove state (0) any progress value wont be parsed, thus is even allowed to contain garbage.\n  It will always emit `{state: 0, value: 0}`.\n- For the set state (1) an omitted value will be set to 0 emitting `{state: 1, value: 0}`.\n  If a value was given, it must be decimal digits only, any characters outside will mark the whole sequence\n  as faulty (no sloppy integer parsing). The value will be clamped to max 100 giving\n  `{state: 1, value: parsedAndClampedValue}`.\n- For the error and pause state (2 & 4) an omitted or zero value will emit `{state: 2|4, value: lastValue}`.\n  If a value was given, it must be decimal digits only, any characters outside will mark the whole sequence\n  as faulty (no sloppy integer parsing). The value will be clamped to max 100 giving\n  `{state: 2|4, value: parsedAndClampedValue}`.\n- For the indeterminate state (3) a value notion will be ignored.\n  It still emits the value as `{state: 3, value: lastValue}`. Keep in mind not use that value while\n  that state is active, as a task might have entered that state without a proper reset at the beginning.\n"
  },
  {
    "path": "addons/addon-progress/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-progress\",\n  \"version\": \"0.2.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-progress.js\",\n  \"module\": \"lib/addon-progress.mjs\",\n  \"types\": \"typings/addon-progress.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-progress\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-progress/src/ProgressAddon.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';\nimport type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';\nimport type { Emitter, IEvent } from 'common/Event';\n\n\nconst enum ProgressType {\n  REMOVE = 0,\n  SET = 1,\n  ERROR = 2,\n  INDETERMINATE = 3,\n  PAUSE = 4\n}\n\n\n/**\n * Strict integer parsing, only decimal digits allowed.\n */\nfunction toInt(s: string): number {\n  let v = 0;\n  for (let i = 0; i < s.length; ++i) {\n    const c = s.charCodeAt(i);\n    if (c < 0x30 || 0x39 < c) {\n      return -1;\n    }\n    v = v * 10 + c - 48;\n  }\n  return v;\n}\n\n\nexport class ProgressAddon implements ITerminalAddon, IProgressApi {\n  private _seqHandler: IDisposable | undefined;\n  private _st: ProgressType = ProgressType.REMOVE;\n  private _pr = 0;\n  // HACK: This uses ! to align with the API, this should be fixed when 5283 is resolved\n  private _onChange!: Emitter<IProgressState>;\n  public onChange!: IEvent<IProgressState>;\n\n  public dispose(): void {\n    this._seqHandler?.dispose();\n    this._onChange?.dispose();\n  }\n\n  public activate(terminal: Terminal): void {\n    this._seqHandler = terminal.parser.registerOscHandler(9, data => {\n      if (!data.startsWith('4;')) {\n        return false;\n      }\n      const parts = data.split(';');\n\n      if (parts.length > 3) {\n        return true;  // faulty sequence, just exit\n      }\n      if (parts.length === 2) {\n        parts.push('');\n      }\n      const st = toInt(parts[1]);\n      const pr = toInt(parts[2]);\n\n      switch (st) {\n        case ProgressType.REMOVE:\n          this.progress = { state: st, value: 0 };\n          break;\n        case ProgressType.SET:\n          if (pr < 0) return true;  // faulty sequence, just exit\n          this.progress = { state: st, value: pr };\n          break;\n        case ProgressType.ERROR:\n        case ProgressType.PAUSE:\n          if (pr < 0) return true;  // faulty sequence, just exit\n          this.progress = { state: st, value: pr || this._pr };\n          break;\n        case ProgressType.INDETERMINATE:\n          this.progress = { state: st, value: this._pr };\n          break;\n      }\n      return true;\n    });\n    // FIXME: borrow emitter ctor from xterm, to be changed once #5283 is resolved\n    this._onChange = new (terminal as any)._core._onData.constructor();\n    this.onChange = this._onChange!.event;\n  }\n\n  public get progress(): IProgressState {\n    return { state: this._st, value: this._pr };\n  }\n\n  public set progress(progress: IProgressState) {\n    if (progress.state < 0 || progress.state > 4) {\n      console.warn(`progress state out of bounds, not applied`);\n      return;\n    }\n    this._st = progress.state;\n    this._pr = Math.min(Math.max(progress.value, 0), 100);\n    this._onChange?.fire({ state: this._st, value: this._pr });\n  }\n}\n"
  },
  {
    "path": "addons/addon-progress/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"@xterm/addon-progress\": [\n        \"../typings/addon-progress.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../src/common\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-progress/test/ProgressAddon.test.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepStrictEqual } from 'assert';\nimport { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';\n\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  ctx.page.setViewportSize({ width: 1024, height: 768 });\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\n\ntest.describe('ProgressAddon', () => {\n  test.beforeEach(async function(): Promise<any> {\n    await ctx.page.evaluate(`\n      window.progressStack = [];\n      window.term.reset();\n      window.progressAddon?.dispose();\n      window.progressAddon = new ProgressAddon();\n      window.term.loadAddon(window.progressAddon);\n      window.progressAddon.onChange(progress => window.progressStack.push(progress));\n    `);\n  });\n\n  test('initial values should be 0;0', async () => {\n    deepStrictEqual(await ctx.page.evaluate('window.progressAddon.progress'), {state: 0, value: 0});\n  });\n  test('state 0: remove', async () => {\n    // no value\n    await ctx.proxy.write('\\x1b]9;4;0\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 0, value: 0}]);\n    // value ignored\n    await ctx.proxy.write('\\x1b]9;4;0;12\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 0, value: 0}, {state: 0, value: 0}]);\n  });\n  test('state 1: set', async () => {\n    // set 10%\n    await ctx.proxy.write('\\x1b]9;4;1;10\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 10}]);\n    // set 50%\n    await ctx.proxy.write('\\x1b]9;4;1;50\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 10}, {state: 1, value: 50}]);\n    // set 23%\n    await ctx.proxy.write('\\x1b]9;4;1;23\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 10}, {state: 1, value: 50}, {state: 1, value: 23}]);\n  });\n  test('state 1: set - special sequence handling', async () => {\n    // missing progress value defaults to 0\n    await ctx.proxy.write('\\x1b]9;4;1\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 0}]);\n    // malformed progress value get ignored\n    await ctx.proxy.write('\\x1b]9;4;1;12x\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 0}]);\n    // out of bounds gets clamped to 100\n    await ctx.proxy.write('\\x1b]9;4;1;123\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 0}, {state: 1, value: 100}]);\n  });\n  test('state 2: error - preserve previous value on empty/0', async () => {\n    // set value to 12\n    await ctx.proxy.write('\\x1b]9;4;1;12\\x1b\\\\');\n    // omitted/empty/0 value emits previous value\n    await ctx.proxy.write('\\x1b]9;4;2\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;2;\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;2;0\\x1b\\\\');\n    deepStrictEqual(\n      await ctx.page.evaluate('window.progressStack'),\n      [{state: 1, value: 12}, {state: 2, value: 12}, {state: 2, value: 12}, {state: 2, value: 12}]\n    );\n  });\n  test('state 2: error - with new value', async () => {\n    // set value to 12\n    await ctx.proxy.write('\\x1b]9;4;1;12\\x1b\\\\');\n    // new value updates clamped\n    await ctx.proxy.write('\\x1b]9;4;2;25\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;2;123\\x1b\\\\');\n    deepStrictEqual(\n      await ctx.page.evaluate('window.progressStack'),\n      [{state: 1, value: 12}, {state: 2, value: 25}, {state: 2, value: 100}]\n    );\n  });\n  test('state 3: indeterminate - keeps value untouched', async () => {\n    // set value to 12\n    await ctx.proxy.write('\\x1b]9;4;1;12\\x1b\\\\');\n    // new value updates clamped\n    await ctx.proxy.write('\\x1b]9;4;3\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;3;123\\x1b\\\\');\n    deepStrictEqual(\n      await ctx.page.evaluate('window.progressStack'),\n      [{state: 1, value: 12}, {state: 3, value: 12}, {state: 3, value: 12}]\n    );\n  });\n  test('state 4: pause - preserve previous value on empty/0', async () => {\n    // set value to 12\n    await ctx.proxy.write('\\x1b]9;4;1;12\\x1b\\\\');\n    // omitted/empty/0 value emits previous value\n    await ctx.proxy.write('\\x1b]9;4;4\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;4;\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;4;0\\x1b\\\\');\n    deepStrictEqual(\n      await ctx.page.evaluate('window.progressStack'),\n      [{state: 1, value: 12}, {state: 4, value: 12}, {state: 4, value: 12}, {state: 4, value: 12}]\n    );\n  });\n  test('state 4: pause - with new value', async () => {\n    // set value to 12\n    await ctx.proxy.write('\\x1b]9;4;1;12\\x1b\\\\');\n    // new value updates clamped\n    await ctx.proxy.write('\\x1b]9;4;4;25\\x1b\\\\');\n    await ctx.proxy.write('\\x1b]9;4;4;123\\x1b\\\\');\n    deepStrictEqual(\n      await ctx.page.evaluate('window.progressStack'),\n      [{state: 1, value: 12}, {state: 4, value: 25}, {state: 4, value: 100}]\n    );\n  });\n  test('invalid sequences should not emit anything', async () => {\n    // illegal state\n    await ctx.proxy.write('\\x1b]9;4;5;12\\x1b\\\\');\n    // illegal chars in value\n    await ctx.proxy.write('\\x1b]9;4;1; 123xxxx\\x1b\\\\');\n    deepStrictEqual(await ctx.page.evaluate('window.progressStack'), []);\n  });\n});\n"
  },
  {
    "path": "addons/addon-progress/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-progress/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-progress/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-progress/typings/addon-progress.d.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon, IDisposable, IEvent } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-progress' {\n  /**\n   * An xterm.js addon that provides an interface for ConEmu's progress\n   * sequence.\n   */\n  export class ProgressAddon implements ITerminalAddon, IDisposable {\n\n    /**\n     * Creates a new progress addon\n     */\n    constructor();\n\n    /**\n     * Activates the addon\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n\n    /**\n     * An event that fires when the tracked progress changes.\n     */\n    public readonly onChange: IEvent<IProgressState>;\n\n    /**\n     * Gets or sets the current progress tracked by the addon. This can be used\n     * to reset a stuck progress indicator back to initial with\n     * `{ state: 0, value: 0 }` or to restore an indicator.\n     */\n    public progress: IProgressState;\n  }\n\n  /**\n   * Progress state tracked by the addon.\n   */\n  export interface IProgressState {\n    /**\n     * The progress state.\n     *\n     * - `0`: No progress. Setting this will resets progress value to 0\n     *   regardless of the {@link value} used.\n     * - `1`: Normal percentage-based from 0 to 100.\n     * - `2`: Error with an optional progress value from 0 to 100.\n     * - `3`: Indeterminate progress, any progress value will be ignored. This\n     *   is used to indicate work is happening but a percentage value cannot be\n     *   determined.\n     * - `4`: Pause or warning state with an optional progress value.\n     */\n    state: 0 | 1 | 2 | 3 | 4;\n\n    /**\n     * The percentage value of progress from 0 to 100. See {@link state} for\n     * whether this is relevant.\n     */\n    value: number;\n  }\n}\n"
  },
  {
    "path": "addons/addon-progress/webpack.config.js",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'ProgressAddon';\nconst mainFile = 'addon-progress.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      browser: path.resolve('../../out/browser'),\n      vs: path.resolve('../../out/vs')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-search/.gitignore",
    "content": "lib\nnode_modules"
  },
  {
    "path": "addons/addon-search/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-search/LICENSE",
    "content": "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-search/README.md",
    "content": "## @xterm/addon-search\n\nAn addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables searching the buffer. This addon requires xterm.js v4+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-search\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { SearchAddon } from '@xterm/addon-search';\n\nconst terminal = new Terminal();\nconst searchAddon = new SearchAddon();\nterminal.loadAddon(searchAddon);\nsearchAddon.findNext('foo');\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-search/typings/addon-search.d.ts) for more advanced usage.\n"
  },
  {
    "path": "addons/addon-search/fixtures/issue-2444",
    "content": "/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\nmake[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake[3]: Nothing to be done for `libsrc/wrap_curl/CMakeFiles/wrap_curl.dir/build'.\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\n[  0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\n[  0%] [  0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\nBuilt target config_utils\n[  0%] make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'\n/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\nBuilt target omega\nmake[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'\ncd /home/yan/ssd/searchtrunk/tmp/release && /usr/bin/cmake -E cmake_depends \"Unix Makefiles\" /home/yan/ssd/searchtrunk /home/yan/ssd/searchtrunk/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp/CMakeFiles/protogen.dir/DependInfo.cmake --color=\n/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\nBuilt target morpheus\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake -f libsrc/base/CMakeFiles/base.dir/build.make libsrc/base/CMakeFiles/base.dir/build\nmake[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake[3]: Nothing to be done for `libsrc/coroutine/CMakeFiles/coroutine.dir/build'.\nBuilt target wrap_curl\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\n/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\n/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\n[  0%] [  0%] [  0%] [  0%] Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o\ncd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++   -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include     -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/crop_context.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp\nBuilding CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o\nBuilding CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o\ncd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++   -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include     -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/detector.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp\nBuilt target coroutine\ncd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++   -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include     -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/image_crop.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp\nmake[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake[3]: Nothing to be done for `libsrc/base/CMakeFiles/base.dir/build'.\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\n/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake -f projects/protogen/cpp/CMakeFiles/protogen.dir/build.make projects/protogen/cpp/CMakeFiles/protogen.dir/build\n[  0%] Built target base\nIn file included from /home/yan/ssd/searchtrunk/include/image_crop/detector.hpp:3:0,\n                 from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp:2:\n/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory\n #include <opencv2/dnn.hpp>\n          ^~~~~~~~~~~~~~~~~\ncompilation terminated.\nmake[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o] Error 1\nmake[3]: *** Waiting for unfinished jobs....\nIn file included from /home/yan/ssd/searchtrunk/include/image_crop/image_crop.hpp:3:0,\n                 from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp:1:\n/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory\n #include <opencv2/dnn.hpp>\n          ^~~~~~~~~~~~~~~~~\ncompilation terminated.\nmake[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o] Error 1\nIn file included from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp:1:0:\n/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory\n #include <opencv2/dnn.hpp>\n          ^~~~~~~~~~~~~~~~~\ncompilation terminated.\nmake[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o] Error 1\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake[2]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/all] Error 2\nmake[2]: *** Waiting for unfinished jobs....\nmake[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake[3]: Nothing to be done for `projects/protogen/cpp/CMakeFiles/protogen.dir/build'.\nmake[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\n/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles  63 64 65 66\n[ 25%] Built target protogen\nmake[2]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'\nmake[1]: *** [all] Error 2\nmake[1]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release/projects/projectX'\nmake: *** [install] Error 2\nyan@yPC:~/ssd/searchtrunk/projects/projectX$\n"
  },
  {
    "path": "addons/addon-search/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-search\",\n  \"version\": \"0.16.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-search.js\",\n  \"module\": \"lib/addon-search.mjs\",\n  \"types\": \"typings/addon-search.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"prepackage\": \"../../node_modules/.bin/tsgo -p .\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/src/DecorationManager.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, IDisposable, IDecoration } from '@xterm/xterm';\nimport type { ISearchDecorationOptions } from '@xterm/addon-search';\nimport { dispose, Disposable, toDisposable } from 'common/Lifecycle';\nimport type { ISearchResult } from './SearchEngine';\n\n/**\n * Interface for managing a highlight decoration.\n */\ninterface IHighlight extends IDisposable {\n  decoration: IDecoration;\n  match: ISearchResult;\n}\n\n/**\n * Interface for managing multiple decorations for a single match.\n */\ninterface IMultiHighlight extends IDisposable {\n  decorations: IDecoration[];\n  match: ISearchResult;\n}\n\n/**\n * Manages visual decorations for search results including highlighting and active selection\n * indicators. This class handles the creation, styling, and disposal of search-related decorations.\n */\nexport class DecorationManager extends Disposable {\n  private _highlightDecorations: IHighlight[] = [];\n  private _highlightedLines: Set<number> = new Set();\n\n  constructor(private readonly _terminal: Terminal) {\n    super();\n    this._register(toDisposable(() => this.clearHighlightDecorations()));\n  }\n\n  /**\n   * Creates decorations for all provided search results.\n   * @param results The search results to create decorations for.\n   * @param options The decoration options.\n   */\n  public createHighlightDecorations(results: ISearchResult[], options: ISearchDecorationOptions): void {\n    this.clearHighlightDecorations();\n\n    for (const match of results) {\n      const decorations = this._createResultDecorations(match, options, false);\n      if (decorations) {\n        for (const decoration of decorations) {\n          this._storeDecoration(decoration, match);\n        }\n      }\n    }\n  }\n\n  /**\n   * Creates decorations for the currently active search result.\n   * @param result The active search result.\n   * @param options The decoration options.\n   * @returns The multi-highlight decoration or undefined if creation failed.\n   */\n  public createActiveDecoration(result: ISearchResult, options: ISearchDecorationOptions): IMultiHighlight | undefined {\n    const decorations = this._createResultDecorations(result, options, true);\n    if (decorations) {\n      return { decorations, match: result, dispose() { dispose(decorations); } };\n    }\n    return undefined;\n  }\n\n  /**\n   * Clears all highlight decorations.\n   */\n  public clearHighlightDecorations(): void {\n    dispose(this._highlightDecorations);\n    this._highlightDecorations = [];\n    this._highlightedLines.clear();\n  }\n\n  /**\n   * Stores a decoration and tracks it for management.\n   * @param decoration The decoration to store.\n   * @param match The search result this decoration represents.\n   */\n  private _storeDecoration(decoration: IDecoration, match: ISearchResult): void {\n    this._highlightedLines.add(decoration.marker.line);\n    this._highlightDecorations.push({ decoration, match, dispose() { decoration.dispose(); } });\n  }\n\n  /**\n   * Applies styles to the decoration when it is rendered.\n   * @param element The decoration's element.\n   * @param borderColor The border color to apply.\n   * @param isActiveResult Whether the element is part of the active search result.\n   */\n  private _applyStyles(element: HTMLElement, borderColor: string | undefined, isActiveResult: boolean): void {\n    if (!element.classList.contains('xterm-find-result-decoration')) {\n      element.classList.add('xterm-find-result-decoration');\n      if (borderColor) {\n        element.style.outline = `1px solid ${borderColor}`;\n      }\n    }\n    if (isActiveResult) {\n      element.classList.add('xterm-find-active-result-decoration');\n    }\n  }\n\n  /**\n   * Creates a decoration for the result and applies styles\n   * @param result the search result for which to create the decoration\n   * @param options the options for the decoration\n   * @param isActiveResult whether this is the currently active result\n   * @returns the decorations or undefined if the marker has already been disposed of\n   */\n  private _createResultDecorations(result: ISearchResult, options: ISearchDecorationOptions, isActiveResult: boolean): IDecoration[] | undefined {\n    // Gather decoration ranges for this match as it could wrap\n    const decorationRanges: [number, number, number][] = [];\n    let currentCol = result.col;\n    let remainingSize = result.size;\n    let markerOffset = -this._terminal.buffer.active.baseY - this._terminal.buffer.active.cursorY + result.row;\n    while (remainingSize > 0) {\n      const amountThisRow = Math.min(this._terminal.cols - currentCol, remainingSize);\n      decorationRanges.push([markerOffset, currentCol, amountThisRow]);\n      currentCol = 0;\n      remainingSize -= amountThisRow;\n      markerOffset++;\n    }\n\n    // Create the decorations\n    const decorations: IDecoration[] = [];\n    for (const range of decorationRanges) {\n      const marker = this._terminal.registerMarker(range[0]);\n      const decoration = this._terminal.registerDecoration({\n        marker,\n        x: range[1],\n        width: range[2],\n        layer: isActiveResult ? 'top' : 'bottom',\n        backgroundColor: isActiveResult ? options.activeMatchBackground : options.matchBackground,\n        overviewRulerOptions: this._highlightedLines.has(marker.line) ? undefined : {\n          color: isActiveResult ? options.activeMatchColorOverviewRuler : options.matchOverviewRuler,\n          position: 'center'\n        }\n      });\n      if (decoration) {\n        const disposables: IDisposable[] = [];\n        disposables.push(marker);\n        disposables.push(decoration.onRender((e) => this._applyStyles(e, isActiveResult ? options.activeMatchBorder : options.matchBorder, false)));\n        disposables.push(decoration.onDispose(() => dispose(disposables)));\n        decorations.push(decoration);\n      }\n    }\n\n    return decorations.length === 0 ? undefined : decorations;\n  }\n}\n\n\n"
  },
  {
    "path": "addons/addon-search/src/SearchAddon.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';\nimport type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search';\nimport { Emitter, type IEvent } from 'common/Event';\nimport { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\nimport { disposableTimeout } from 'common/Async';\nimport { SearchLineCache } from './SearchLineCache';\nimport { SearchState } from './SearchState';\nimport { SearchEngine, type ISearchResult } from './SearchEngine';\nimport { DecorationManager } from './DecorationManager';\nimport { SearchResultTracker } from './SearchResultTracker';\n\ninterface IInternalSearchOptions {\n  noScroll: boolean;\n}\n\n/**\n * Configuration constants for the search addon functionality.\n */\nconst enum Constants {\n  /**\n   * Default maximum number of search results to highlight simultaneously. This limit prevents\n   * performance degradation when searching for very common terms that would result in excessive\n   * highlighting decorations.\n   */\n  DEFAULT_HIGHLIGHT_LIMIT = 1000\n}\n\nexport class SearchAddon extends Disposable implements ITerminalAddon, ISearchApi {\n  private _terminal: Terminal | undefined;\n  private _highlightLimit: number;\n  private _highlightTimeout = this._register(new MutableDisposable<IDisposable>());\n  private _lineCache = this._register(new MutableDisposable<SearchLineCache>());\n\n  // Component instances\n  private _state = new SearchState();\n  private _engine: SearchEngine | undefined;\n  private _decorationManager: DecorationManager | undefined;\n  private _resultTracker = this._register(new SearchResultTracker());\n\n  private readonly _onAfterSearch = this._register(new Emitter<void>());\n  public readonly onAfterSearch = this._onAfterSearch.event;\n  private readonly _onBeforeSearch = this._register(new Emitter<void>());\n  public readonly onBeforeSearch = this._onBeforeSearch.event;\n\n  public get onDidChangeResults(): IEvent<ISearchResultChangeEvent> {\n    return this._resultTracker.onDidChangeResults;\n  }\n\n  constructor(options?: Partial<ISearchAddonOptions>) {\n    super();\n\n    this._highlightLimit = options?.highlightLimit ?? Constants.DEFAULT_HIGHLIGHT_LIMIT;\n  }\n\n  public activate(terminal: Terminal): void {\n    this._terminal = terminal;\n    this._lineCache.value = new SearchLineCache(terminal);\n    this._engine = new SearchEngine(terminal, this._lineCache.value);\n    this._decorationManager = new DecorationManager(terminal);\n    this._register(this._terminal.onWriteParsed(() => this._updateMatches()));\n    this._register(this._terminal.onResize(() => this._updateMatches()));\n    this._register(toDisposable(() => this.clearDecorations()));\n  }\n\n  private _updateMatches(): void {\n    this._highlightTimeout.clear();\n    if (this._state.cachedSearchTerm && this._state.lastSearchOptions?.decorations) {\n      this._highlightTimeout.value = disposableTimeout(() => {\n        const term = this._state.cachedSearchTerm;\n        this._state.clearCachedTerm();\n        this.findPrevious(term!, { ...this._state.lastSearchOptions, incremental: true }, { noScroll: true });\n      }, 200);\n    }\n  }\n\n  public clearDecorations(retainCachedSearchTerm?: boolean): void {\n    this._resultTracker.clearSelectedDecoration();\n    this._decorationManager?.clearHighlightDecorations();\n    this._resultTracker.clearResults();\n    if (!retainCachedSearchTerm) {\n      this._state.clearCachedTerm();\n    }\n  }\n\n  public clearActiveDecoration(): void {\n    this._resultTracker.clearSelectedDecoration();\n  }\n\n  /**\n   * Find the next instance of the term, then scroll to and select it. If it\n   * doesn't exist, do nothing.\n   * @param term The search term.\n   * @param searchOptions Search options.\n   * @returns Whether a result was found.\n   */\n  public findNext(term: string, searchOptions?: ISearchOptions, internalSearchOptions?: IInternalSearchOptions): boolean {\n    if (!this._terminal || !this._engine) {\n      throw new Error('Cannot use addon until it has been loaded');\n    }\n\n    this._onBeforeSearch.fire();\n\n    this._state.lastSearchOptions = searchOptions;\n\n    if (this._state.shouldUpdateHighlighting(term, searchOptions)) {\n      this._highlightAllMatches(term, searchOptions!);\n    }\n\n    const found = this._findNextAndSelect(term, searchOptions, internalSearchOptions);\n    this._fireResults(searchOptions);\n    this._state.cachedSearchTerm = term;\n\n    this._onAfterSearch.fire();\n\n    return found;\n  }\n\n  private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void {\n    if (!this._terminal || !this._engine || !this._decorationManager) {\n      throw new Error('Cannot use addon until it has been loaded');\n    }\n    if (!this._state.isValidSearchTerm(term)) {\n      this.clearDecorations();\n      return;\n    }\n\n    // new search, clear out the old decorations\n    this.clearDecorations(true);\n\n    const results: ISearchResult[] = [];\n    let prevResult: ISearchResult | undefined = undefined;\n    let result = this._engine.find(term, 0, 0, searchOptions);\n\n    while (result && (prevResult?.row !== result.row || prevResult?.col !== result.col)) {\n      if (results.length >= this._highlightLimit) {\n        break;\n      }\n      prevResult = result;\n      results.push(prevResult);\n      result = this._engine.find(\n        term,\n        prevResult.col + prevResult.term.length >= this._terminal.cols ? prevResult.row + 1 : prevResult.row,\n        prevResult.col + prevResult.term.length >= this._terminal.cols ? 0 : prevResult.col + 1,\n        searchOptions\n      );\n    }\n\n    this._resultTracker.updateResults(results, this._highlightLimit);\n    if (searchOptions.decorations) {\n      this._decorationManager.createHighlightDecorations(results, searchOptions.decorations);\n    }\n  }\n\n  private _findNextAndSelect(term: string, searchOptions?: ISearchOptions, internalSearchOptions?: IInternalSearchOptions): boolean {\n    if (!this._terminal || !this._engine) {\n      return false;\n    }\n    if (!this._state.isValidSearchTerm(term)) {\n      this._terminal.clearSelection();\n      this.clearDecorations();\n      return false;\n    }\n\n    const result = this._engine.findNextWithSelection(term, searchOptions, this._state.cachedSearchTerm);\n    return this._selectResult(result, searchOptions?.decorations, internalSearchOptions?.noScroll);\n  }\n\n  /**\n   * Find the previous instance of the term, then scroll to and select it. If it\n   * doesn't exist, do nothing.\n   * @param term The search term.\n   * @param searchOptions Search options.\n   * @returns Whether a result was found.\n   */\n  public findPrevious(term: string, searchOptions?: ISearchOptions, internalSearchOptions?: IInternalSearchOptions): boolean {\n    if (!this._terminal || !this._engine) {\n      throw new Error('Cannot use addon until it has been loaded');\n    }\n\n    this._onBeforeSearch.fire();\n\n    this._state.lastSearchOptions = searchOptions;\n\n    if (this._state.shouldUpdateHighlighting(term, searchOptions)) {\n      this._highlightAllMatches(term, searchOptions!);\n    }\n\n    const found = this._findPreviousAndSelect(term, searchOptions, internalSearchOptions);\n    this._fireResults(searchOptions);\n    this._state.cachedSearchTerm = term;\n\n    this._onAfterSearch.fire();\n\n    return found;\n  }\n\n  private _fireResults(searchOptions?: ISearchOptions): void {\n    this._resultTracker.fireResultsChanged(!!searchOptions?.decorations);\n  }\n\n  private _findPreviousAndSelect(term: string, searchOptions?: ISearchOptions, internalSearchOptions?: IInternalSearchOptions): boolean {\n    if (!this._terminal || !this._engine) {\n      return false;\n    }\n    if (!this._state.isValidSearchTerm(term)) {\n      this._terminal.clearSelection();\n      this.clearDecorations();\n      return false;\n    }\n\n    const result = this._engine.findPreviousWithSelection(term, searchOptions, this._state.cachedSearchTerm);\n    return this._selectResult(result, searchOptions?.decorations, internalSearchOptions?.noScroll);\n  }\n\n  /**\n   * Selects and scrolls to a result.\n   * @param result The result to select.\n   * @returns Whether a result was selected.\n   */\n  private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean {\n    if (!this._terminal || !this._decorationManager) {\n      return false;\n    }\n\n    this._resultTracker.clearSelectedDecoration();\n    if (!result) {\n      this._terminal.clearSelection();\n      return false;\n    }\n\n    this._terminal.select(result.col, result.row, result.size);\n    if (options) {\n      const activeDecoration = this._decorationManager.createActiveDecoration(result, options);\n      if (activeDecoration) {\n        this._resultTracker.selectedDecoration = activeDecoration;\n      }\n    }\n\n    if (!noScroll) {\n      // If it is not in the viewport then we scroll else it just gets selected\n      if (result.row >= (this._terminal.buffer.active.viewportY + this._terminal.rows) || result.row < this._terminal.buffer.active.viewportY) {\n        let scroll = result.row - this._terminal.buffer.active.viewportY;\n        scroll -= Math.floor(this._terminal.rows / 2);\n        this._terminal.scrollLines(scroll);\n      }\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/src/SearchEngine.test.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { SearchEngine } from './SearchEngine';\nimport { SearchLineCache } from './SearchLineCache';\nimport { Terminal } from 'browser/public/Terminal';\nimport type { ISearchOptions } from '@xterm/addon-search';\nimport { DisposableStore } from 'common/Lifecycle';\n\nfunction writeP(terminal: Terminal, data: string): Promise<void> {\n  return new Promise(r => terminal.write(data, r));\n}\n\ndescribe('SearchEngine', () => {\n  let store: DisposableStore;\n  let terminal: Terminal;\n  let lineCache: SearchLineCache;\n  let searchEngine: SearchEngine;\n\n  beforeEach(() => {\n    store = new DisposableStore();\n    terminal = store.add(new Terminal({ cols: 80, rows: 24 }));\n    lineCache = store.add(new SearchLineCache(terminal));\n    searchEngine = new SearchEngine(terminal, lineCache);\n  });\n\n  afterEach(() => {\n    store.dispose();\n  });\n\n  describe('find', () => {\n    it('should return undefined for empty search term', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.find('', 0, 0), undefined);\n    });\n\n    it('should find basic text in terminal content', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.deepStrictEqual(searchEngine.find('World', 0, 0), {\n        term: 'World',\n        col: 6,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should find text starting from specified position', async () => {\n      await writeP(terminal, 'Hello Hello Hello');\n\n      assert.deepStrictEqual(searchEngine.find('Hello', 0, 7), {\n        term: 'Hello',\n        col: 12,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should search across multiple rows', async () => {\n      await writeP(terminal, 'Line 1\\r\\nLine 2 target\\r\\nLine 3');\n\n      assert.deepStrictEqual(searchEngine.find('target', 0, 0), {\n        term: 'target',\n        col: 7,\n        row: 1,\n        size: 6\n      });\n    });\n\n    it('should return undefined when text is not found', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.find('NotFound', 0, 0), undefined);\n    });\n\n    it('should throw error for invalid column position', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.throws(() => {\n        searchEngine.find('Hello', 0, 100);\n      }, /Invalid col: 100 to search in terminal of 80 cols/);\n    });\n\n    it('should handle search starting from last column', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.find('Hello', 0, 79), undefined);\n    });\n\n    it('should handle search from middle of match', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.find('llo', 0, 3), undefined); // Should not find partial match that starts before search position\n    });\n  });\n\n  describe('search options', () => {\n    describe('caseSensitive', () => {\n      it('should find text with case-insensitive search (default)', async () => {\n        await writeP(terminal, 'Hello WORLD');\n\n        assert.deepStrictEqual(searchEngine.find('world', 0, 0), {\n          term: 'world',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should find text with case-sensitive search when enabled', async () => {\n        await writeP(terminal, 'Hello WORLD');\n\n        assert.deepStrictEqual(searchEngine.find('WORLD', 0, 0, { caseSensitive: true }), {\n          term: 'WORLD',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should not find text with case-sensitive search when case differs', async () => {\n        await writeP(terminal, 'Hello WORLD');\n\n        assert.strictEqual(searchEngine.find('world', 0, 0, { caseSensitive: true }), undefined);\n      });\n    });\n\n    describe('wholeWord', () => {\n      it('should find whole word when enabled', async () => {\n        await writeP(terminal, 'Hello world wonderful');\n\n        assert.deepStrictEqual(searchEngine.find('world', 0, 0, { wholeWord: true }), {\n          term: 'world',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should not find partial word when wholeWord is enabled', async () => {\n        await writeP(terminal, 'Hello wonderful');\n\n        assert.strictEqual(searchEngine.find('world', 0, 0, { wholeWord: true }), undefined);\n      });\n\n      it('should find word at beginning of line with wholeWord', async () => {\n        await writeP(terminal, 'world is great');\n\n        assert.deepStrictEqual(searchEngine.find('world', 0, 0, { wholeWord: true }), {\n          term: 'world',\n          col: 0,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should find word at end of line with wholeWord', async () => {\n        await writeP(terminal, 'hello world');\n\n        assert.deepStrictEqual(searchEngine.find('world', 0, 0, { wholeWord: true }), {\n          term: 'world',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should handle word boundaries with punctuation', async () => {\n        await writeP(terminal, 'hello,world!test');\n\n        assert.deepStrictEqual(searchEngine.find('world', 0, 0, { wholeWord: true }), {\n          term: 'world',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should not match when not whole word', async () => {\n        await writeP(terminal, 'helloworld');\n\n        assert.strictEqual(searchEngine.find('world', 0, 0, { wholeWord: true }), undefined);\n      });\n    });\n\n    describe('regex', () => {\n      it('should find text using simple regex pattern', async () => {\n        await writeP(terminal, 'Hello 123 World');\n\n        assert.deepStrictEqual(searchEngine.find('[0-9]+', 0, 0, { regex: true }), {\n          term: '123',\n          col: 6,\n          row: 0,\n          size: 3\n        });\n      });\n\n      it('should find text using regex with case-insensitive flag', async () => {\n        await writeP(terminal, 'Hello WORLD');\n\n        assert.deepStrictEqual(searchEngine.find('world', 0, 0, { regex: true, caseSensitive: false }), {\n          term: 'WORLD',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should find text using regex with case-sensitive flag', async () => {\n        await writeP(terminal, 'Hello WORLD world');\n\n        assert.deepStrictEqual(searchEngine.find('WORLD', 0, 0, { regex: true, caseSensitive: true }), {\n          term: 'WORLD',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should handle complex regex patterns', async () => {\n        await writeP(terminal, 'Email: test@example.com and another@domain.org');\n\n        assert.deepStrictEqual(searchEngine.find('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}', 0, 0, { regex: true }), {\n          term: 'test@example.com',\n          col: 7,\n          row: 0,\n          size: 16\n        });\n      });\n\n      it('should return undefined for invalid regex pattern', async () => {\n        await writeP(terminal, 'Hello World');\n\n        // Invalid regex should be handled gracefully\n        assert.throws(() => {\n          searchEngine.find('[invalid', 0, 0, { regex: true });\n        }, /Invalid regular expression/);\n      });\n\n      it('should handle empty regex matches', async () => {\n        await writeP(terminal, 'Hello World');\n\n        assert.strictEqual(searchEngine.find('.*?', 0, 0, { regex: true }), undefined); // Empty matches should be ignored\n      });\n    });\n\n    describe('combined options', () => {\n      it('should handle regex + caseSensitive combination', async () => {\n        await writeP(terminal, 'Hello WORLD world');\n\n        assert.deepStrictEqual(searchEngine.find('[A-Z]+', 0, 0, { regex: true, caseSensitive: true }), {\n          term: 'H',\n          col: 0,\n          row: 0,\n          size: 1\n        });\n      });\n\n      it('should handle wholeWord + caseSensitive combination', async () => {\n        await writeP(terminal, 'Hello WORLD wonderful');\n\n        const result1 = searchEngine.find('WORLD', 0, 0, { wholeWord: true, caseSensitive: true });\n        assert.deepStrictEqual(result1, {\n          term: 'WORLD',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n\n        const result2 = searchEngine.find('world', 0, 0, { wholeWord: true, caseSensitive: true });\n        assert.strictEqual(result2, undefined);\n      });\n    });\n  });\n\n  describe('findNextWithSelection', () => {\n    it('should return undefined for empty search term', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.findNextWithSelection(''), undefined);\n    });\n\n    it('should find first occurrence when no selection exists', async () => {\n      await writeP(terminal, 'Hello World Hello');\n\n      assert.deepStrictEqual(searchEngine.findNextWithSelection('Hello'), {\n        term: 'Hello',\n        col: 0,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should find next occurrence after current selection', async () => {\n      await writeP(terminal, 'Hello World Hello Again');\n\n      // Mock the getSelectionPosition to return a selection at first \"Hello\"\n      terminal.getSelectionPosition = () => ({ start: { x: 0, y: 0 }, end: { x: 5, y: 0 } });\n\n      assert.deepStrictEqual(searchEngine.findNextWithSelection('Hello', undefined, 'Hello'), {\n        term: 'Hello',\n        col: 12,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should wrap around to beginning when reaching end', async () => {\n      await writeP(terminal, 'Hello World Hello');\n\n      // Mock selection at the end\n      terminal.getSelectionPosition = () => ({ start: { x: 12, y: 0 }, end: { x: 17, y: 0 } });\n\n      assert.deepStrictEqual(searchEngine.findNextWithSelection('Hello', undefined, 'Hello'), {\n        term: 'Hello',\n        col: 0,\n        row: 0,\n        size: 5\n      }); // Should wrap to first occurrence\n    });\n\n    it('should wrap across multiple rows', async () => {\n      await writeP(terminal, 'Line 1 test\\r\\nLine 2\\r\\nLine 3 test');\n\n      // Mock selection at first \"test\"\n      terminal.getSelectionPosition = () => ({ start: { x: 7, y: 0 }, end: { x: 11, y: 0 } });\n\n      assert.deepStrictEqual(searchEngine.findNextWithSelection('test', undefined, 'test'), {\n        term: 'test',\n        col: 7,\n        row: 2,\n        size: 4\n      });\n    });\n\n    it('should return same selection if only one match exists', async () => {\n      await writeP(terminal, 'Hello World');\n\n      // Mock selection at \"Hello\"\n      terminal.getSelectionPosition = () => ({ start: { x: 0, y: 0 }, end: { x: 5, y: 0 } });\n\n      assert.deepStrictEqual(searchEngine.findNextWithSelection('Hello'), {\n        term: 'Hello',\n        col: 0,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should clear selection and return undefined when term not found', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.findNextWithSelection('NotFound'), undefined);\n    });\n  });\n\n  describe('findPreviousWithSelection', () => {\n    it('should return undefined for empty search term', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.findPreviousWithSelection(''), undefined);\n    });\n\n    it('should find last occurrence when no selection exists', async () => {\n      await writeP(terminal, 'Hello World Hello');\n\n      assert.deepStrictEqual(searchEngine.findPreviousWithSelection('Hello'), {\n        term: 'Hello',\n        col: 12,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should find previous occurrence before current selection', async () => {\n      await writeP(terminal, 'Hello World Hello Again');\n\n      // Mock selection at second \"Hello\"\n      terminal.getSelectionPosition = () => ({ start: { x: 12, y: 0 }, end: { x: 17, y: 0 } });\n\n      const result = searchEngine.findPreviousWithSelection('Hello');\n      assert.notStrictEqual(result, undefined);\n      // It may find the same selection first due to expansion attempt\n      assert.strictEqual(typeof result!.col, 'number');\n      assert.strictEqual(result!.row, 0);\n    });\n\n    it('should wrap around to end when reaching beginning', async () => {\n      await writeP(terminal, 'Hello World Hello');\n\n      // Mock selection at first \"Hello\"\n      terminal.getSelectionPosition = () => ({ start: { x: 0, y: 0 }, end: { x: 5, y: 0 } });\n\n      const result = searchEngine.findPreviousWithSelection('Hello');\n      assert.notStrictEqual(result, undefined);\n      // Due to the expansion attempt, it may find the same Hello first\n      assert.strictEqual(typeof result!.col, 'number');\n      assert.strictEqual(result!.row, 0);\n    });\n\n    it('should work across multiple rows in reverse', async () => {\n      await writeP(terminal, 'test Line 1\\r\\nLine 2\\r\\ntest Line 3');\n\n      // Mock selection at last \"test\"\n      terminal.getSelectionPosition = () => ({ start: { x: 0, y: 2 }, end: { x: 4, y: 2 } });\n\n      const result = searchEngine.findPreviousWithSelection('test');\n      assert.notStrictEqual(result, undefined);\n      // The algorithm will find the current selection first due to expansion attempt\n      assert.strictEqual(typeof result!.row, 'number');\n      assert.strictEqual(typeof result!.col, 'number');\n    });\n\n    it('should handle selection expansion correctly', async () => {\n      await writeP(terminal, 'Hello World Hello');\n\n      // Mock selection at first \"Hello\"\n      terminal.getSelectionPosition = () => ({ start: { x: 0, y: 0 }, end: { x: 5, y: 0 } });\n\n      const result = searchEngine.findPreviousWithSelection('Hello');\n      assert.notStrictEqual(result, undefined);\n      // The algorithm tries expansion first, so it may find the same Hello\n      assert.strictEqual(typeof result!.col, 'number');\n      assert.strictEqual(result!.row, 0);\n    });\n\n    it('should clear selection and return undefined when term not found', async () => {\n      await writeP(terminal, 'Hello World');\n\n      assert.strictEqual(searchEngine.findPreviousWithSelection('NotFound'), undefined);\n    });\n  });\n\n  describe('edge cases and error handling', () => {\n    describe('unicode and special characters', () => {\n      it('should handle unicode characters correctly', async () => {\n        await writeP(terminal, 'Hello 世界 World');\n\n        assert.deepStrictEqual(searchEngine.find('世界', 0, 0), {\n          term: '世界',\n          col: 6,\n          row: 0,\n          size: 4\n        });\n      });\n\n\n\n      it('should handle wide characters', async () => {\n        await writeP(terminal, '中文测试');\n\n        assert.deepStrictEqual(searchEngine.find('测试', 0, 0), {\n          term: '测试',\n          col: 4,\n          row: 0,\n          size: 4\n        });\n      });\n\n\n    });\n\n    describe('wrapped lines', () => {\n      it('should handle search across wrapped lines', async () => {\n        const longText = 'A'.repeat(100) + 'target' + 'B'.repeat(50);\n        await writeP(terminal, longText);\n\n        assert.deepStrictEqual(searchEngine.find('target', 0, 0), {\n          term: 'target',\n          col: 20,\n          row: 1,\n          size: 6\n        });\n      });\n\n      it('should handle wrapped lines with unicode', async () => {\n        const longText = '中'.repeat(50) + 'target' + '文'.repeat(30);\n        await writeP(terminal, longText);\n\n        assert.deepStrictEqual(searchEngine.find('target', 0, 0), {\n          term: 'target',\n          col: 20,\n          row: 1,\n          size: 6\n        });\n      });\n\n      it('should skip wrapped lines correctly in findInLine', async () => {\n        const longText = 'A'.repeat(200);\n        await writeP(terminal, longText + '\\r\\nNext line with target');\n\n        assert.deepStrictEqual(searchEngine.find('target', 0, 0), {\n          term: 'target',\n          col: 15,\n          row: 3,\n          size: 6\n        });\n      });\n    });\n\n    describe('buffer boundaries', () => {\n\n\n      it('should handle empty buffer gracefully', () => {\n        assert.strictEqual(searchEngine.find('anything', 0, 0), undefined);\n      });\n\n      it('should handle search beyond buffer size', () => {\n        assert.strictEqual(searchEngine.find('test', 1000, 0), undefined);\n      });\n    });\n\n    describe('invalid inputs', () => {\n      it('should handle undefined search options gracefully', async () => {\n        await writeP(terminal, 'Hello World');\n\n        assert.deepStrictEqual(searchEngine.find('Hello', 0, 0, undefined), {\n          term: 'Hello',\n          col: 0,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should handle negative start positions', async () => {\n        await writeP(terminal, 'Hello World');\n\n        assert.deepStrictEqual(searchEngine.find('Hello', -1, -1), {\n          term: 'Hello',\n          col: 0,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should handle search options with undefined properties', async () => {\n        await writeP(terminal, 'Hello World');\n\n        const options: ISearchOptions = {\n          caseSensitive: undefined,\n          regex: undefined,\n          wholeWord: undefined\n        };\n\n        assert.deepStrictEqual(searchEngine.find('Hello', 0, 0, options), {\n          term: 'Hello',\n          col: 0,\n          row: 0,\n          size: 5\n        });\n      });\n    });\n  });\n\n  describe('private method behaviors (tested indirectly)', () => {\n    describe('_isWholeWord behavior', () => {\n      it('should recognize word boundaries with various punctuation', async () => {\n        await writeP(terminal, 'word1 word2,word3(word4)word5[word6]word7{word8}');\n\n        const tests = [\n          { term: 'word1', expected: true },\n          { term: 'word2', expected: true },\n          { term: 'word3', expected: true },\n          { term: 'word4', expected: true },\n          { term: 'word5', expected: true },\n          { term: 'word6', expected: true },\n          { term: 'word7', expected: true },\n          { term: 'word8', expected: true }\n        ];\n\n        for (const test of tests) {\n          const result = searchEngine.find(test.term, 0, 0, { wholeWord: true });\n          if (test.expected) {\n            assert.notStrictEqual(result, undefined, `Should find whole word: ${test.term}`);\n          } else {\n            assert.strictEqual(result, undefined, `Should not find non-whole word: ${test.term}`);\n          }\n        }\n      });\n\n      it('should handle word boundaries at line start and end', async () => {\n        await writeP(terminal, 'start middle end');\n\n        const startResult = searchEngine.find('start', 0, 0, { wholeWord: true });\n        assert.deepStrictEqual(startResult, {\n          term: 'start',\n          col: 0,\n          row: 0,\n          size: 5\n        });\n\n        const endResult = searchEngine.find('end', 0, 0, { wholeWord: true });\n        assert.deepStrictEqual(endResult, {\n          term: 'end',\n          col: 13,\n          row: 0,\n          size: 3\n        });\n\n        const middleResult = searchEngine.find('middle', 0, 0, { wholeWord: true });\n        assert.deepStrictEqual(middleResult, {\n          term: 'middle',\n          col: 6,\n          row: 0,\n          size: 6\n        });\n      });\n    });\n\n    describe('buffer offset calculations', () => {\n      it('should handle wide character offset calculations', async () => {\n        await writeP(terminal, '中文 test 测试');\n\n        const result = searchEngine.find('test', 0, 0);\n        assert.notStrictEqual(result, undefined);\n        assert.strictEqual(result!.term, 'test');\n        // Exact column position depends on wide character handling\n        assert.strictEqual(typeof result!.col, 'number');\n      });\n\n\n\n\n    });\n\n    describe('string to buffer size conversions', () => {\n      it('should correctly calculate size for simple text', async () => {\n        await writeP(terminal, 'Hello World');\n\n        assert.deepStrictEqual(searchEngine.find('World', 0, 0), {\n          term: 'World',\n          col: 6,\n          row: 0,\n          size: 5\n        });\n      });\n\n      it('should correctly calculate size for unicode text', async () => {\n        await writeP(terminal, 'Hello 世界');\n\n        const result = searchEngine.find('世界', 0, 0);\n        assert.notStrictEqual(result, undefined);\n        // Size should account for wide characters\n        assert.strictEqual(typeof result!.size, 'number');\n        assert.strictEqual(result!.size >= 2, true);\n      });\n\n      it('should handle size calculation across wrapped lines', async () => {\n        const longMatch = 'A'.repeat(100);\n        await writeP(terminal, longMatch);\n\n        const result = searchEngine.find(longMatch, 0, 0);\n        assert.notStrictEqual(result, undefined);\n        assert.strictEqual(result!.size >= 100, true);\n      });\n    });\n  });\n\n  describe('integration with SearchLineCache', () => {\n    it('should use cache for line translation', async () => {\n      await writeP(terminal, 'Hello World');\n\n      // Initialize cache\n      lineCache.initLinesCache();\n\n      const result1 = searchEngine.find('World', 0, 0);\n      const result2 = searchEngine.find('World', 0, 0);\n\n      assert.notStrictEqual(result1, undefined);\n      assert.notStrictEqual(result2, undefined);\n      assert.deepStrictEqual(result1, result2);\n    });\n\n    it('should handle cache misses gracefully', async () => {\n      await writeP(terminal, 'Hello World');\n\n      // Don't initialize cache\n      assert.deepStrictEqual(searchEngine.find('World', 0, 0), {\n        term: 'World',\n        col: 6,\n        row: 0,\n        size: 5\n      });\n    });\n\n    it('should work correctly with cache invalidation', async () => {\n      await writeP(terminal, 'Initial text');\n      lineCache.initLinesCache();\n\n      const result1 = searchEngine.find('Initial', 0, 0);\n      assert.notStrictEqual(result1, undefined);\n\n      // Change terminal content which should invalidate cache\n      await writeP(terminal, '\\r\\nNew line');\n\n      const result2 = searchEngine.find('New', 0, 0);\n      assert.deepStrictEqual(result2, {\n        term: 'New',\n        col: 0,\n        row: 1,\n        size: 3\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "addons/addon-search/src/SearchEngine.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal } from '@xterm/xterm';\nimport type { ISearchOptions } from '@xterm/addon-search';\nimport type { SearchLineCache } from './SearchLineCache';\n\n/**\n * Represents the position to start a search from.\n */\ninterface ISearchPosition {\n  startCol: number;\n  startRow: number;\n}\n\n/**\n * Represents a search result with its position and content.\n */\nexport interface ISearchResult {\n  term: string;\n  col: number;\n  row: number;\n  size: number;\n}\n\n/**\n * Configuration constants for the search engine functionality.\n */\nconst enum Constants {\n  /**\n   * Characters that are considered non-word characters for search boundary detection. These\n   * characters are used to determine word boundaries when performing whole-word searches. Includes\n   * common punctuation, symbols, and whitespace characters.\n   */\n  NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\\\;:\"\\',./<>?'\n}\n\n/**\n * Core search engine that handles finding text within terminal content.\n * This class is responsible for the actual search algorithms and position calculations.\n */\nexport class SearchEngine {\n  constructor(\n    private readonly _terminal: Terminal,\n    private readonly _lineCache: SearchLineCache\n  ) {}\n\n  /**\n   * Find the first occurrence of a term starting from a specific position.\n   * @param term The search term.\n   * @param startRow The row to start searching from.\n   * @param startCol The column to start searching from.\n   * @param searchOptions Search options.\n   * @returns The search result if found, undefined otherwise.\n   */\n  public find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {\n    if (!term || term.length === 0) {\n      this._terminal.clearSelection();\n      return undefined;\n    }\n    if (startCol > this._terminal.cols) {\n      throw new Error(`Invalid col: ${startCol} to search in terminal of ${this._terminal.cols} cols`);\n    }\n\n    this._lineCache.initLinesCache();\n\n    const searchPosition: ISearchPosition = {\n      startRow,\n      startCol\n    };\n\n    // Search startRow\n    let result = this._findInLine(term, searchPosition, searchOptions);\n    // Search from startRow + 1 to end\n    if (!result) {\n      for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {\n        searchPosition.startRow = y;\n        searchPosition.startCol = 0;\n        result = this._findInLine(term, searchPosition, searchOptions);\n        if (result) {\n          break;\n        }\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Find the next occurrence of a term with wrapping and selection management.\n   * @param term The search term.\n   * @param searchOptions Search options.\n   * @param cachedSearchTerm The cached search term to determine incremental behavior.\n   * @returns The search result if found, undefined otherwise.\n   */\n  public findNextWithSelection(term: string, searchOptions?: ISearchOptions, cachedSearchTerm?: string): ISearchResult | undefined {\n    if (!term || term.length === 0) {\n      this._terminal.clearSelection();\n      return undefined;\n    }\n\n    const prevSelectedPos = this._terminal.getSelectionPosition();\n    this._terminal.clearSelection();\n\n    let startCol = 0;\n    let startRow = 0;\n    if (prevSelectedPos) {\n      if (cachedSearchTerm === term) {\n        startCol = prevSelectedPos.end.x;\n        startRow = prevSelectedPos.end.y;\n      } else {\n        startCol = prevSelectedPos.start.x;\n        startRow = prevSelectedPos.start.y;\n      }\n    }\n\n    this._lineCache.initLinesCache();\n\n    const searchPosition: ISearchPosition = {\n      startRow,\n      startCol\n    };\n\n    // Search startRow\n    let result = this._findInLine(term, searchPosition, searchOptions);\n    // Search from startRow + 1 to end\n    if (!result) {\n      for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {\n        searchPosition.startRow = y;\n        searchPosition.startCol = 0;\n        result = this._findInLine(term, searchPosition, searchOptions);\n        if (result) {\n          break;\n        }\n      }\n    }\n    // If we hit the bottom and didn't search from the very top wrap back up\n    if (!result && startRow !== 0) {\n      for (let y = 0; y < startRow; y++) {\n        searchPosition.startRow = y;\n        searchPosition.startCol = 0;\n        result = this._findInLine(term, searchPosition, searchOptions);\n        if (result) {\n          break;\n        }\n      }\n    }\n\n    // If there is only one result, wrap back and return selection if it exists.\n    if (!result && prevSelectedPos) {\n      searchPosition.startRow = prevSelectedPos.start.y;\n      searchPosition.startCol = 0;\n      result = this._findInLine(term, searchPosition, searchOptions);\n    }\n\n    return result;\n  }\n\n  /**\n   * Find the previous occurrence of a term with wrapping and selection management.\n   * @param term The search term.\n   * @param searchOptions Search options.\n   * @param cachedSearchTerm The cached search term to determine if expansion should occur.\n   * @returns The search result if found, undefined otherwise.\n   */\n  public findPreviousWithSelection(term: string, searchOptions?: ISearchOptions, cachedSearchTerm?: string): ISearchResult | undefined {\n    if (!term || term.length === 0) {\n      this._terminal.clearSelection();\n      return undefined;\n    }\n\n    const prevSelectedPos = this._terminal.getSelectionPosition();\n    this._terminal.clearSelection();\n\n    let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;\n    let startCol = this._terminal.cols;\n    const isReverseSearch = true;\n\n    this._lineCache.initLinesCache();\n    const searchPosition: ISearchPosition = {\n      startRow,\n      startCol\n    };\n\n    let result: ISearchResult | undefined;\n    if (prevSelectedPos) {\n      searchPosition.startRow = startRow = prevSelectedPos.start.y;\n      searchPosition.startCol = startCol = prevSelectedPos.start.x;\n      if (cachedSearchTerm !== term) {\n        // Try to expand selection to right first.\n        result = this._findInLine(term, searchPosition, searchOptions, false);\n        if (!result) {\n          // If selection was not able to be expanded to the right, then try reverse search\n          searchPosition.startRow = startRow = prevSelectedPos.end.y;\n          searchPosition.startCol = startCol = prevSelectedPos.end.x;\n        }\n      }\n    }\n\n    result ??= this._findInLine(term, searchPosition, searchOptions, isReverseSearch);\n\n    // Search from startRow - 1 to top\n    if (!result) {\n      searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols);\n      for (let y = startRow - 1; y >= 0; y--) {\n        searchPosition.startRow = y;\n        result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);\n        if (result) {\n          break;\n        }\n      }\n    }\n    // If we hit the top and didn't search from the very bottom wrap back down\n    if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows - 1)) {\n      for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows - 1); y >= startRow; y--) {\n        searchPosition.startRow = y;\n        result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);\n        if (result) {\n          break;\n        }\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * A found substring is a whole word if it doesn't have an alphanumeric character directly\n   * adjacent to it.\n   * @param searchIndex starting index of the potential whole word substring\n   * @param line entire string in which the potential whole word was found\n   * @param term the substring that starts at searchIndex\n   */\n  private _isWholeWord(searchIndex: number, line: string, term: string): boolean {\n    return ((searchIndex === 0) || (Constants.NON_WORD_CHARACTERS.includes(line[searchIndex - 1]))) &&\n      (((searchIndex + term.length) === line.length) || (Constants.NON_WORD_CHARACTERS.includes(line[searchIndex + term.length])));\n  }\n\n  /**\n   * Searches a line for a search term. Takes the provided terminal line and searches the text line,\n   * which may contain subsequent terminal lines if the text is wrapped. If the provided line number\n   * is part of a wrapped text line that started on an earlier line then it is skipped since it will\n   * be properly searched when the terminal line that the text starts on is searched.\n   * @param term The search term.\n   * @param searchPosition The position to start the search.\n   * @param searchOptions Search options.\n   * @param isReverseSearch Whether the search should start from the right side of the terminal and\n   * search to the left.\n   * @returns The search result if it was found.\n   */\n  private _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {\n    const row = searchPosition.startRow;\n    const col = searchPosition.startCol;\n\n    // Ignore wrapped lines, only consider on unwrapped line (first row of command string).\n    const firstLine = this._terminal.buffer.active.getLine(row);\n    if (firstLine?.isWrapped) {\n      if (isReverseSearch) {\n        searchPosition.startCol += this._terminal.cols;\n        return;\n      }\n\n      // This will iterate until we find the line start.\n      // When we find it, we will search using the calculated start column.\n      searchPosition.startRow--;\n      searchPosition.startCol += this._terminal.cols;\n      return this._findInLine(term, searchPosition, searchOptions);\n    }\n    let cache = this._lineCache.getLineFromCache(row);\n    if (!cache) {\n      cache = this._lineCache.translateBufferLineToStringWithWrap(row, true);\n      this._lineCache.setLineInCache(row, cache);\n    }\n    const [stringLine, offsets] = cache;\n\n    const offset = this._bufferColsToStringOffset(row, col);\n    let searchTerm = term;\n    let searchStringLine = stringLine;\n    if (!searchOptions.regex) {\n      searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();\n      searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();\n    }\n\n    let resultIndex = -1;\n    if (searchOptions.regex) {\n      const searchRegex = RegExp(searchTerm, searchOptions.caseSensitive ? 'g' : 'gi');\n      let foundTerm: RegExpExecArray | null;\n      if (isReverseSearch) {\n        // This loop will get the resultIndex of the _last_ regex match in the range 0..offset\n        while (foundTerm = searchRegex.exec(searchStringLine.slice(0, offset))) {\n          resultIndex = searchRegex.lastIndex - foundTerm[0].length;\n          term = foundTerm[0];\n          searchRegex.lastIndex -= (term.length - 1);\n        }\n      } else {\n        foundTerm = searchRegex.exec(searchStringLine.slice(offset));\n        if (foundTerm && foundTerm[0].length > 0) {\n          resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length);\n          term = foundTerm[0];\n        }\n      }\n    } else {\n      if (isReverseSearch) {\n        if (offset - searchTerm.length >= 0) {\n          resultIndex = searchStringLine.lastIndexOf(searchTerm, offset - searchTerm.length);\n        }\n      } else {\n        resultIndex = searchStringLine.indexOf(searchTerm, offset);\n      }\n    }\n\n    if (resultIndex >= 0) {\n      if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {\n        return;\n      }\n\n      // Adjust the row number and search index if needed since a \"line\" of text can span multiple\n      // rows\n      let startRowOffset = 0;\n      while (startRowOffset < offsets.length - 1 && resultIndex >= offsets[startRowOffset + 1]) {\n        startRowOffset++;\n      }\n      let endRowOffset = startRowOffset;\n      while (endRowOffset < offsets.length - 1 && resultIndex + term.length >= offsets[endRowOffset + 1]) {\n        endRowOffset++;\n      }\n      const startColOffset = resultIndex - offsets[startRowOffset];\n      const endColOffset = resultIndex + term.length - offsets[endRowOffset];\n      const startColIndex = this._stringLengthToBufferSize(row + startRowOffset, startColOffset);\n      const endColIndex = this._stringLengthToBufferSize(row + endRowOffset, endColOffset);\n      const size = endColIndex - startColIndex + this._terminal.cols * (endRowOffset - startRowOffset);\n\n      return {\n        term,\n        col: startColIndex,\n        row: row + startRowOffset,\n        size\n      };\n    }\n  }\n\n  private _stringLengthToBufferSize(row: number, offset: number): number {\n    const line = this._terminal.buffer.active.getLine(row);\n    if (!line) {\n      return 0;\n    }\n    for (let i = 0; i < offset; i++) {\n      const cell = line.getCell(i);\n      if (!cell) {\n        break;\n      }\n      // Adjust the searchIndex to normalize emoji into single chars\n      const char = cell.getChars();\n      if (char.length > 1) {\n        offset -= char.length - 1;\n      }\n      // Adjust the searchIndex for empty characters following wide unicode\n      // chars (eg. CJK)\n      const nextCell = line.getCell(i + 1);\n      if (nextCell && nextCell.getWidth() === 0) {\n        offset++;\n      }\n    }\n    return offset;\n  }\n\n  private _bufferColsToStringOffset(startRow: number, cols: number): number {\n    let lineIndex = startRow;\n    let offset = 0;\n    let line = this._terminal.buffer.active.getLine(lineIndex);\n    while (cols > 0 && line) {\n      for (let i = 0; i < cols && i < this._terminal.cols; i++) {\n        const cell = line.getCell(i);\n        if (!cell) {\n          break;\n        }\n        if (cell.getWidth()) {\n          // Treat null characters as whitespace to align with the translateToString API\n          offset += cell.getCode() === 0 ? 1 : cell.getChars().length;\n        }\n      }\n      lineIndex++;\n      line = this._terminal.buffer.active.getLine(lineIndex);\n      if (line && !line.isWrapped) {\n        break;\n      }\n      cols -= this._terminal.cols;\n    }\n    return offset;\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/src/SearchLineCache.test.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { SearchLineCache, LineCacheEntry } from './SearchLineCache';\nimport { Terminal } from 'browser/public/Terminal';\nimport { timeout } from 'common/Async';\n\nfunction writeP(terminal: Terminal, data: string): Promise<void> {\n  return new Promise(r => terminal.write(data, r));\n}\n\ndescribe('SearchLineCache', () => {\n  let terminal: Terminal;\n  let cache: SearchLineCache;\n\n  beforeEach(() => {\n    terminal = new Terminal({ cols: 80, rows: 24 });\n    cache = new SearchLineCache(terminal);\n  });\n\n  afterEach(() => {\n    cache.dispose();\n    terminal.dispose();\n  });\n\n  describe('constructor', () => {\n    it('should create a SearchLineCache instance', () => {\n      assert.instanceOf(cache, SearchLineCache);\n    });\n\n    it('should start with no cache initialized', () => {\n      assert.equal(cache.getLineFromCache(0), undefined);\n    });\n  });\n\n  describe('initLinesCache', () => {\n    it('should initialize the lines cache array', () => {\n      cache.initLinesCache();\n      assert.equal(cache.getLineFromCache(0), undefined, 'cache should be initialized but empty');\n    });\n\n    it('should not reinitialize if cache already exists', () => {\n      cache.initLinesCache();\n      cache.setLineInCache(0, ['test', [0]]);\n\n      cache.initLinesCache();\n\n      const entry = cache.getLineFromCache(0);\n      assert.deepEqual(entry, ['test', [0]], 'cache should still contain the previously set entry');\n    });\n\n    it('should set up TTL timeout', () => {\n      cache.initLinesCache();\n      cache.setLineInCache(0, ['test', [0]]);\n\n      assert.deepEqual(cache.getLineFromCache(0), ['test', [0]], 'entry should exist after initialization');\n    });\n  });\n\n  describe('getLineFromCache', () => {\n    it('should return undefined when cache is not initialized', () => {\n      assert.equal(cache.getLineFromCache(0), undefined);\n      assert.equal(cache.getLineFromCache(10), undefined);\n    });\n\n    it('should return undefined for unset entries when cache is initialized', () => {\n      cache.initLinesCache();\n      assert.equal(cache.getLineFromCache(0), undefined);\n      assert.equal(cache.getLineFromCache(50), undefined);\n    });\n\n    it('should return cached entries', () => {\n      cache.initLinesCache();\n      const entry: LineCacheEntry = ['test content', [0]];\n      cache.setLineInCache(5, entry);\n\n      assert.deepEqual(cache.getLineFromCache(5), entry);\n    });\n  });\n\n  describe('setLineInCache', () => {\n    it('should not set entries when cache is not initialized', () => {\n      const entry: LineCacheEntry = ['test content', [0]];\n      cache.setLineInCache(0, entry);\n\n      assert.equal(cache.getLineFromCache(0), undefined);\n    });\n\n    it('should set entries when cache is initialized', () => {\n      cache.initLinesCache();\n      const entry: LineCacheEntry = ['test content', [0]];\n      cache.setLineInCache(10, entry);\n\n      assert.deepEqual(cache.getLineFromCache(10), entry);\n    });\n\n    it('should overwrite existing entries', () => {\n      cache.initLinesCache();\n      const entry1: LineCacheEntry = ['first content', [0]];\n      const entry2: LineCacheEntry = ['second content', [0]];\n\n      cache.setLineInCache(0, entry1);\n      assert.deepEqual(cache.getLineFromCache(0), entry1);\n\n      cache.setLineInCache(0, entry2);\n      assert.deepEqual(cache.getLineFromCache(0), entry2);\n    });\n  });\n\n  describe('translateBufferLineToStringWithWrap', () => {\n    it('should translate a single line without wrapping', async () => {\n      await writeP(terminal, 'Hello World');\n      const result = cache.translateBufferLineToStringWithWrap(0, true);\n      assert.equal(result[0], 'Hello World');\n      assert.deepEqual(result[1], [0]);\n    });\n\n    it('should handle trimRight parameter', async () => {\n      await writeP(terminal, 'Hello World   ');\n      const resultTrimmed = cache.translateBufferLineToStringWithWrap(0, true);\n      const resultNotTrimmed = cache.translateBufferLineToStringWithWrap(0, false);\n\n      assert.equal(resultTrimmed[0].trimEnd(), 'Hello World');\n      assert.isTrue(resultNotTrimmed[0].startsWith('Hello World   '));\n      assert.isTrue(resultNotTrimmed[0].length > resultTrimmed[0].length, 'non-trimmed result should be longer');\n    });\n\n    it('should handle wrapped lines', async () => {\n      const longText = 'A'.repeat(200);\n      await writeP(terminal, longText);\n      const result = cache.translateBufferLineToStringWithWrap(0, true);\n      assert.equal(result[0], longText);\n      assert.isTrue(result[1].length > 1, 'should have multiple offsets due to wrapping');\n      assert.equal(result[1][0], 0, 'first offset should be 0');\n    });\n\n    it('should handle wide characters', async () => {\n      await writeP(terminal, 'Hello 世界');\n      const result = cache.translateBufferLineToStringWithWrap(0, true);\n      assert.equal(result[0], 'Hello 世界');\n      assert.deepEqual(result[1], [0]);\n    });\n\n    it('should handle empty lines', () => {\n      const result = cache.translateBufferLineToStringWithWrap(0, true);\n      assert.equal(result[0], '');\n      assert.deepEqual(result[1], [0]);\n    });\n\n    it('should handle lines beyond buffer', () => {\n      const result = cache.translateBufferLineToStringWithWrap(1000, true);\n      assert.equal(result[0], '');\n      assert.deepEqual(result[1], [0]);\n    });\n\n    it('should handle complex wrapped content', async () => {\n      await writeP(terminal, 'Line 1\\r\\n');\n      await writeP(terminal, 'Line 2 with some longer content that might wrap\\r\\n');\n      await writeP(terminal, 'Line 3');\n\n      const result1 = cache.translateBufferLineToStringWithWrap(0, true);\n      const result2 = cache.translateBufferLineToStringWithWrap(1, true);\n      const result3 = cache.translateBufferLineToStringWithWrap(2, true);\n\n      assert.equal(result1[0], 'Line 1');\n      assert.equal(result2[0], 'Line 2 with some longer content that might wrap');\n      assert.equal(result3[0], 'Line 3');\n    });\n  });\n\n  describe('cache invalidation', () => {\n    it('should invalidate cache on line feed', async () => {\n      cache.initLinesCache();\n      cache.setLineInCache(0, ['test', [0]]);\n\n      assert.deepEqual(cache.getLineFromCache(0), ['test', [0]]);\n\n      terminal.write('test\\r\\n');\n\n      await timeout(10);\n      assert.equal(cache.getLineFromCache(0), undefined);\n    });\n\n    it('should invalidate cache on cursor move', async () => {\n      cache.initLinesCache();\n      cache.setLineInCache(0, ['test', [0]]);\n\n      assert.deepEqual(cache.getLineFromCache(0), ['test', [0]]);\n\n      await writeP(terminal, 'some text');\n      await timeout(10);\n      assert.equal(cache.getLineFromCache(0), undefined);\n    });\n\n    it('should invalidate cache on resize', async () => {\n      cache.initLinesCache();\n      cache.setLineInCache(0, ['test', [0]]);\n\n      assert.deepEqual(cache.getLineFromCache(0), ['test', [0]]);\n\n      terminal.resize(100, 30);\n\n      await timeout(10);\n      assert.equal(cache.getLineFromCache(0), undefined);\n    });\n  });\n\n  describe('disposal', () => {\n    it('should clean up resources on dispose', () => {\n      cache.initLinesCache();\n      cache.setLineInCache(0, ['test', [0]]);\n\n      assert.deepEqual(cache.getLineFromCache(0), ['test', [0]]);\n\n      cache.dispose();\n\n      assert.equal(cache.getLineFromCache(0), undefined, 'cache should be destroyed after disposal');\n    });\n\n    it('should be safe to dispose multiple times', () => {\n      cache.initLinesCache();\n      cache.dispose();\n      cache.dispose();\n\n      assert.equal(cache.getLineFromCache(0), undefined);\n    });\n  });\n\n  describe('LineCacheEntry type', () => {\n    it('should handle complex line offsets', () => {\n      const entry: LineCacheEntry = [\n        'A very long line that wraps multiple times across several terminal lines',\n        [0, 20, 40, 60]\n      ];\n\n      cache.initLinesCache();\n      cache.setLineInCache(0, entry);\n\n      const retrieved = cache.getLineFromCache(0);\n      assert.deepEqual(retrieved, entry);\n      assert.equal(retrieved![0].length, 72);\n      assert.equal(retrieved![1].length, 4);\n    });\n\n    it('should handle unicode characters in cache entries', () => {\n      const entry: LineCacheEntry = [\n        'Hello 世界 🌍 测试',\n        [0]\n      ];\n\n      cache.initLinesCache();\n      cache.setLineInCache(0, entry);\n\n      const retrieved = cache.getLineFromCache(0);\n      assert.deepEqual(retrieved, entry);\n      assert.equal(retrieved![0], 'Hello 世界 🌍 测试');\n    });\n  });\n\n  describe('integration with real terminal content', () => {\n    it('should correctly translate real buffer content', async () => {\n      await writeP(terminal, 'Hello World');\n      const cached = cache.translateBufferLineToStringWithWrap(0, true);\n      const directTranslation = terminal.buffer.active.getLine(0)?.translateToString(true) || '';\n\n      assert.equal(cached[0], directTranslation);\n    });\n\n    it('should handle real wrapped content correctly', async () => {\n      const longContent = 'This is a very long line that will definitely wrap around in an 80 column terminal and should be handled correctly by the cache';\n      await writeP(terminal, longContent);\n      const result = cache.translateBufferLineToStringWithWrap(0, true);\n      assert.equal(result[0], longContent);\n      assert.isTrue(result[1].length > 1, 'should have wrapped');\n    });\n\n    it('should work with real escape sequences', async () => {\n      await writeP(terminal, 'Before\\x1b[31mRed Text\\x1b[0mAfter');\n      const result = cache.translateBufferLineToStringWithWrap(0, true);\n      assert.include(result[0], 'Before');\n      assert.include(result[0], 'Red Text');\n      assert.include(result[0], 'After');\n    });\n  });\n});\n"
  },
  {
    "path": "addons/addon-search/src/SearchLineCache.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal } from '@xterm/xterm';\nimport { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\nimport { disposableTimeout } from 'common/Async';\n\nexport type LineCacheEntry = [\n  /**\n   * The string representation of a line (as opposed to the buffer cell representation).\n   */\n  lineAsString: string,\n  /**\n   * The offsets where each line starts when the entry describes a wrapped line.\n   */\n  lineOffsets: number[]\n];\n\n/**\n * Configuration constants for the search line cache functionality.\n */\nconst enum Constants {\n  /**\n   * Time-to-live for cached search results in milliseconds. After this duration, cached search\n   * results will be invalidated to ensure they remain consistent with terminal content changes.\n   */\n  LINES_CACHE_TIME_TO_LIVE = 15000\n}\n\nexport class SearchLineCache extends Disposable {\n  /**\n   * translateBufferLineToStringWithWrap is a fairly expensive call.\n   * We memoize the calls into an array that has a time based ttl.\n   * _linesCache is also invalidated when the terminal cursor moves.\n   */\n  private _linesCache: LineCacheEntry[] | undefined;\n  private _linesCacheTimeout = this._register(new MutableDisposable());\n  private _linesCacheDisposables = this._register(new MutableDisposable());\n  // Track access to avoid recreating a timeout on every init call which occurs once per search\n  // result (findNext/findPrevious -> _highlightAllMatches -> find loop).\n  private _lastAccessTimestamp = 0;\n\n  constructor(private readonly _terminal: Terminal) {\n    super();\n    this._register(toDisposable(() => this._destroyLinesCache()));\n  }\n\n  /**\n   * Sets up a line cache with a ttl\n   */\n  public initLinesCache(): void {\n    if (!this._linesCache) {\n      this._linesCache = new Array(this._terminal.buffer.active.length);\n      this._linesCacheDisposables.value = combinedDisposable(\n        this._terminal.onLineFeed(() => this._destroyLinesCache()),\n        this._terminal.onCursorMove(() => this._destroyLinesCache()),\n        this._terminal.onResize(() => this._destroyLinesCache())\n      );\n    }\n\n    this._lastAccessTimestamp = Date.now();\n    if (!this._linesCacheTimeout.value) {\n      this._scheduleLinesCacheTimeout(Constants.LINES_CACHE_TIME_TO_LIVE);\n    }\n  }\n\n  private _destroyLinesCache(): void {\n    this._linesCache = undefined;\n    this._lastAccessTimestamp = 0;\n    this._linesCacheDisposables.clear();\n    this._linesCacheTimeout.clear();\n  }\n\n  private _scheduleLinesCacheTimeout(delay: number): void {\n    this._linesCacheTimeout.value = disposableTimeout(() => {\n      if (!this._linesCache) {\n        return;\n      }\n      const now = Date.now();\n      const elapsed = now - this._lastAccessTimestamp;\n      if (elapsed >= Constants.LINES_CACHE_TIME_TO_LIVE) {\n        this._destroyLinesCache();\n        return;\n      }\n      this._scheduleLinesCacheTimeout(Constants.LINES_CACHE_TIME_TO_LIVE - elapsed);\n    }, delay);\n  }\n\n  public getLineFromCache(row: number): LineCacheEntry | undefined {\n    return this._linesCache?.[row];\n  }\n\n  public setLineInCache(row: number, entry: LineCacheEntry): void {\n    if (this._linesCache) {\n      this._linesCache[row] = entry;\n    }\n  }\n\n  /**\n   * Translates a buffer line to a string, including subsequent lines if they are wraps.\n   * Wide characters will count as two columns in the resulting string. This\n   * function is useful for getting the actual text underneath the raw selection\n   * position.\n   * @param lineIndex The index of the line being translated.\n   * @param trimRight Whether to trim whitespace to the right.\n   */\n  public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): LineCacheEntry {\n    const strings = [];\n    const lineOffsets = [0];\n    let line = this._terminal.buffer.active.getLine(lineIndex);\n    while (line) {\n      const nextLine = this._terminal.buffer.active.getLine(lineIndex + 1);\n      const lineWrapsToNext = nextLine ? nextLine.isWrapped : false;\n      let string = line.translateToString(!lineWrapsToNext && trimRight);\n      if (lineWrapsToNext && nextLine) {\n        const lastCell = line.getCell(line.length - 1);\n        const lastCellIsNull = lastCell && lastCell.getCode() === 0 && lastCell.getWidth() === 1;\n        // a wide character wrapped to the next line\n        if (lastCellIsNull && nextLine.getCell(0)?.getWidth() === 2) {\n          string = string.slice(0, -1);\n        }\n      }\n      strings.push(string);\n      if (lineWrapsToNext) {\n        lineOffsets.push(lineOffsets[lineOffsets.length - 1] + string.length);\n      } else {\n        break;\n      }\n      lineIndex++;\n      line = nextLine;\n    }\n    return [strings.join(''), lineOffsets];\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/src/SearchResultTracker.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { ISearchResultChangeEvent } from '@xterm/addon-search';\nimport type { IDisposable } from '@xterm/xterm';\nimport { Emitter, type IEvent } from 'common/Event';\nimport { Disposable } from 'common/Lifecycle';\nimport type { ISearchResult } from './SearchEngine';\n\n/**\n * Interface for managing a currently selected decoration.\n */\ninterface ISelectedDecoration extends IDisposable {\n  match: ISearchResult;\n}\n\n/**\n * Tracks search results, manages result indexing, and fires events when results change.\n * This class provides centralized management of search result state and notifications.\n */\nexport class SearchResultTracker extends Disposable {\n  private _searchResults: ISearchResult[] = [];\n  private _selectedDecoration: ISelectedDecoration | undefined;\n\n  private readonly _onDidChangeResults = this._register(new Emitter<ISearchResultChangeEvent>());\n  public get onDidChangeResults(): IEvent<ISearchResultChangeEvent> { return this._onDidChangeResults.event; }\n\n  /**\n   * Gets the current search results.\n   */\n  public get searchResults(): ReadonlyArray<ISearchResult> {\n    return this._searchResults;\n  }\n\n  /**\n   * Gets the currently selected decoration.\n   */\n  public get selectedDecoration(): ISelectedDecoration | undefined {\n    return this._selectedDecoration;\n  }\n\n  /**\n   * Sets the currently selected decoration.\n   */\n  public set selectedDecoration(decoration: ISelectedDecoration | undefined) {\n    this._selectedDecoration = decoration;\n  }\n\n  /**\n   * Updates the search results with a new set of results.\n   * @param results The new search results.\n   * @param maxResults The maximum number of results to track.\n   */\n  public updateResults(results: ISearchResult[], maxResults: number): void {\n    this._searchResults = results.slice(0, maxResults);\n  }\n\n  /**\n   * Clears all search results.\n   */\n  public clearResults(): void {\n    this._searchResults = [];\n  }\n\n  /**\n   * Clears the selected decoration.\n   */\n  public clearSelectedDecoration(): void {\n    if (this._selectedDecoration) {\n      this._selectedDecoration.dispose();\n      this._selectedDecoration = undefined;\n    }\n  }\n\n  /**\n   * Finds the index of a result in the current results array.\n   * @param result The result to find.\n   * @returns The index of the result, or -1 if not found.\n   */\n  public findResultIndex(result: ISearchResult): number {\n    for (let i = 0; i < this._searchResults.length; i++) {\n      const match = this._searchResults[i];\n      if (match.row === result.row && match.col === result.col && match.size === result.size) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  /**\n   * Fires a result change event with the current state.\n   * @param hasDecorations Whether decorations are enabled.\n   */\n  public fireResultsChanged(hasDecorations: boolean): void {\n    if (!hasDecorations) {\n      return;\n    }\n\n    let resultIndex = -1;\n    if (this._selectedDecoration) {\n      resultIndex = this.findResultIndex(this._selectedDecoration.match);\n    }\n\n    this._onDidChangeResults.fire({\n      resultIndex,\n      resultCount: this._searchResults.length\n    });\n  }\n\n  /**\n   * Resets all state.\n   */\n  public reset(): void {\n    this.clearSelectedDecoration();\n    this.clearResults();\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/src/SearchState.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { ISearchOptions } from '@xterm/addon-search';\n\n/**\n * Manages search state including cached search terms, options tracking, and validation.\n * This class provides a centralized way to handle search state consistency and option changes.\n */\nexport class SearchState {\n  private _cachedSearchTerm: string | undefined;\n  private _lastSearchOptions: ISearchOptions | undefined;\n\n  /**\n   * Gets the currently cached search term.\n   */\n  public get cachedSearchTerm(): string | undefined {\n    return this._cachedSearchTerm;\n  }\n\n  /**\n   * Sets the cached search term.\n   */\n  public set cachedSearchTerm(term: string | undefined) {\n    this._cachedSearchTerm = term;\n  }\n\n  /**\n   * Gets the last search options used.\n   */\n  public get lastSearchOptions(): ISearchOptions | undefined {\n    return this._lastSearchOptions;\n  }\n\n  /**\n   * Sets the last search options used.\n   */\n  public set lastSearchOptions(options: ISearchOptions | undefined) {\n    this._lastSearchOptions = options;\n  }\n\n  /**\n   * Validates a search term to ensure it's not empty or invalid.\n   * @param term The search term to validate.\n   * @returns true if the term is valid for searching.\n   */\n  public isValidSearchTerm(term: string): boolean {\n    return !!(term && term.length > 0);\n  }\n\n  /**\n   * Determines if search options have changed compared to the last search.\n   * @param newOptions The new search options to compare.\n   * @returns true if the options have changed.\n   */\n  public didOptionsChange(newOptions?: ISearchOptions): boolean {\n    if (!this._lastSearchOptions) {\n      return true;\n    }\n    if (!newOptions) {\n      return false;\n    }\n    if (this._lastSearchOptions.caseSensitive !== newOptions.caseSensitive) {\n      return true;\n    }\n    if (this._lastSearchOptions.regex !== newOptions.regex) {\n      return true;\n    }\n    if (this._lastSearchOptions.wholeWord !== newOptions.wholeWord) {\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Determines if a new search should trigger highlighting updates.\n   * @param term The search term.\n   * @param options The search options.\n   * @returns true if highlighting should be updated.\n   */\n  public shouldUpdateHighlighting(term: string, options?: ISearchOptions): boolean {\n    if (!options?.decorations) {\n      return false;\n    }\n    return this._cachedSearchTerm === undefined ||\n           term !== this._cachedSearchTerm ||\n           this.didOptionsChange(options);\n  }\n\n  /**\n   * Clears the cached search term.\n   */\n  public clearCachedTerm(): void {\n    this._cachedSearchTerm = undefined;\n  }\n\n  /**\n   * Resets all state.\n   */\n  public reset(): void {\n    this._cachedSearchTerm = undefined;\n    this._lastSearchOptions = undefined;\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"@xterm/addon-search\" : [\n        \"../typings/addon-search.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-search/test/SearchAddon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepStrictEqual, strictEqual } from 'assert';\nimport { readFile } from 'fs';\nimport { resolve } from 'path';\nimport { ITestContext, createTestContext, openTerminal, timeout } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx, { cols: 80, rows: 24 });\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('Search Tests', () => {\n\n  test.beforeEach(async () => {\n    await ctx.proxy.reset();\n    await ctx.page.evaluate(`\n      window.search?.dispose();\n      window.search = new SearchAddon();\n      window.term.loadAddon(window.search);\n    `);\n  });\n\n  test('Simple Search', async () => {\n    await ctx.proxy.write('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('test')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), 'test');\n  });\n\n  test('Scrolling Search', async () => {\n    let dataString = '';\n    for (let i = 0; i < 100; i++) {\n      if (i === 52) {\n        dataString += '$^1_3{}test$#';\n      }\n      dataString += makeData(50);\n    }\n    await ctx.proxy.write(dataString);\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), '$^1_3{}test$#');\n  });\n  test('Incremental Find Previous', async () => {\n    await ctx.proxy.writeln(`package.jsonc\\n`);\n    await ctx.proxy.write('package.json pack package.lock');\n    await ctx.page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);\n    let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = (await ctx.proxy.getSelectionPosition())!;\n    let line: string = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();\n    // We look further ahead in the line to ensure that pack was selected from package.lock\n    deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock');\n    await ctx.page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);\n    selectionPosition = (await ctx.proxy.getSelectionPosition())!;\n    deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json');\n    await ctx.page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);\n    // We have to reevaluate line because it should have switched starting rows at this point\n    selectionPosition = (await ctx.proxy.getSelectionPosition())!;\n    line = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();\n    deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc');\n  });\n  test('Incremental Find Next', async () => {\n    await ctx.proxy.writeln(`package.lock pack package.json package.ups\\n`);\n    await ctx.proxy.write('package.jsonc');\n    await ctx.page.evaluate(`window.search.findNext('pack', {incremental: true})`);\n    let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = (await ctx.proxy.getSelectionPosition())!;\n    let line: string = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();\n    // We look further ahead in the line to ensure that pack was selected from package.lock\n    deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock');\n    await ctx.page.evaluate(`window.search.findNext('package.j', {incremental: true})`);\n    selectionPosition = (await ctx.proxy.getSelectionPosition())!;\n    deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json');\n    await ctx.page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);\n    // We have to reevaluate line because it should have switched starting rows at this point\n    selectionPosition = (await ctx.proxy.getSelectionPosition())!;\n    line = await (await ctx.proxy.buffer.active.getLine(selectionPosition.start.y))!.translateToString();\n    deepStrictEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc');\n  });\n  test('Simple Regex', async () => {\n    await ctx.proxy.write('abc123defABCD');\n    await ctx.page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);\n    deepStrictEqual(await ctx.proxy.getSelection(), 'abc');\n    await ctx.page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);\n    deepStrictEqual(await ctx.proxy.getSelection(), 'ABCD');\n  });\n\n  test('Search for single result twice should not unselect it', async () => {\n    await ctx.proxy.write('abc def');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), 'abc');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), 'abc');\n  });\n\n  test('Search for result bounding with wide unicode chars', async () => {\n    await ctx.proxy.write('中文xx𝄞𝄞');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('中')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), '中');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('xx')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), 'xx');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('𝄞')`), true);\n    deepStrictEqual(await ctx.proxy.getSelection(), '𝄞');\n    deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('𝄞')`), true);\n    deepStrictEqual(await ctx.proxy.getSelectionPosition(), {\n      start: {\n        x: 7,\n        y: 0\n      },\n      end: {\n        x: 8,\n        y: 0\n      }\n    });\n  });\n\n  test.describe('onDidChangeResults', async () => {\n    test.describe('findNext', () => {\n      test('should not fire unless the decorations option is set', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('abc');\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('a')`), true);\n        strictEqual(await ctx.page.evaluate('window.calls.length'), 0);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        strictEqual(await ctx.page.evaluate('window.calls.length'), 1);\n      });\n      test('should fire with correct event values', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('abc bc c');\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 0, resultIndex: -1 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 0, resultIndex: -1 },\n          { resultCount: 3, resultIndex: 0 },\n          { resultCount: 3, resultIndex: 1 },\n          { resultCount: 3, resultIndex: 2 }\n        ]);\n      });\n      test('should fire with correct event values (incremental)', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('d abc aabc d');\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 0 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 0, resultIndex: -1 }\n        ]);\n      });\n      test('should fire with more than 1k matches', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        const data = ('a bc'.repeat(10) + '\\\\n\\\\r').repeat(150);\n        await ctx.proxy.write(data);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1000, resultIndex: 0 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1000, resultIndex: 0 },\n          { resultCount: 1000, resultIndex: 1 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1000, resultIndex: 0 },\n          { resultCount: 1000, resultIndex: 1 },\n          { resultCount: 1000, resultIndex: 1 }\n        ]);\n      });\n      test('should fire when writing to terminal', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('abc bc c\\\\n\\\\r'.repeat(2));\n        strictEqual(await ctx.page.evaluate(`window.search.findNext('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 2, resultIndex: 0 }\n        ]);\n        await ctx.proxy.write('abc bc c\\\\n\\\\r');\n        await timeout(300);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 3, resultIndex: 0 }\n        ]);\n      });\n    });\n    test.describe('findPrevious', () => {\n      test('should not fire unless the decorations option is set', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('abc');\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a')`), true);\n        strictEqual(await ctx.page.evaluate('window.calls.length'), 0);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        strictEqual(await ctx.page.evaluate('window.calls.length'), 1);\n      });\n      test('should fire with correct event values', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('abc bc c');\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 }\n        ]);\n        await ctx.page.evaluate(`window.term.clearSelection()`);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        await timeout(2000);\n        strictEqual(await ctx.page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 0, resultIndex: -1 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 0, resultIndex: -1 },\n          { resultCount: 3, resultIndex: 2 },\n          { resultCount: 3, resultIndex: 1 },\n          { resultCount: 3, resultIndex: 0 }\n        ]);\n      });\n      test('should fire with correct event values (incremental)', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('d abc aabc d');\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 2 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 2 },\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 2 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 2 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 0 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('d', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 2 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 3, resultIndex: 2 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 2, resultIndex: 0 },\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 0, resultIndex: -1 }\n        ]);\n      });\n      test('should fire with more than 1k matches', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        const data = ('a bc'.repeat(10) + '\\\\n\\\\r').repeat(150);\n        await ctx.proxy.write(data);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1000, resultIndex: -1 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1000, resultIndex: -1 },\n          { resultCount: 1000, resultIndex: -1 }\n        ]);\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('bc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 1000, resultIndex: -1 },\n          { resultCount: 1000, resultIndex: -1 },\n          { resultCount: 1000, resultIndex: -1 }\n        ]);\n      });\n      test('should fire when writing to terminal', async () => {\n        await ctx.page.evaluate(`\n          window.calls = [];\n          window.search.onDidChangeResults(e => window.calls.push(e));\n        `);\n        await ctx.proxy.write('abc bc c\\\\n\\\\r'.repeat(2));\n        strictEqual(await ctx.page.evaluate(`window.search.findPrevious('abc', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 2, resultIndex: 1 }\n        ]);\n        await ctx.proxy.write('abc bc c\\\\n\\\\r');\n        await timeout(300);\n        deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n          { resultCount: 2, resultIndex: 1 },\n          { resultCount: 3, resultIndex: 1 }\n        ]);\n      });\n    });\n  });\n\n  test.describe('onBeforeSearch and onAfterSearch', () => {\n    test.beforeEach(async () => {\n      await ctx.page.evaluate(`\n        window.events = [];\n        window.search.onBeforeSearch(() => window.events.push('before'));\n        window.search.onAfterSearch(() => window.events.push('after'));\n      `);\n    });\n    test('should fire before and after findNext', async () => {\n      await ctx.proxy.write('abc');\n      await ctx.page.evaluate(`window.search.findNext('a')`);\n      deepStrictEqual(await ctx.page.evaluate('window.events'), ['before', 'after']);\n    });\n\n    test('should fire before and after findPrevious', async () => {\n      await ctx.proxy.write('abc');\n      await ctx.page.evaluate(`window.search.findPrevious('a')`);\n      deepStrictEqual(await ctx.page.evaluate('window.events'), ['before', 'after']);\n    });\n\n    test('should fire for each search call', async () => {\n      await ctx.proxy.write('abc abc');\n      await ctx.page.evaluate(`window.search.findNext('abc')`);\n      await ctx.page.evaluate(`window.search.findNext('abc')`);\n      deepStrictEqual(await ctx.page.evaluate('window.events'), ['before', 'after', 'before', 'after']);\n    });\n  });\n\n  test.describe('Regression tests', () => {\n    test.describe('#2444 wrapped line content not being found', () => {\n      let fixture: string;\n      test.beforeAll(async () => {\n        fixture = (await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)))).toString();\n        if (process.platform !== 'win32') {\n          fixture = fixture.replace(/\\n/g, '\\n\\r');\n        }\n      });\n      test('should find all occurrences using findNext', async () => {\n        await ctx.proxy.write(fixture);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        let selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });\n        // Wrap around to first result\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findNext('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });\n      });\n\n      test('should y all occurrences using findPrevious', async () => {\n        await ctx.proxy.write(fixture);\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        let selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } });\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } });\n        // Wrap around to first result\n        deepStrictEqual(await ctx.page.evaluate(`window.search.findPrevious('opencv')`), true);\n        selectionPosition = await ctx.proxy.getSelectionPosition();\n        deepStrictEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } });\n      });\n    });\n  });\n  test.describe('#3834 lines with null characters before search terms', () => {\n    // This case can be triggered by the prompt when using starship under conpty\n    test('should find all matches on a line containing null characters', async () => {\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.search.onDidChangeResults(e => window.calls.push(e));\n      `);\n      // Move cursor forward 1 time to create a null character, as opposed to regular whitespace\n      await ctx.proxy.write('\\\\x1b[CHi Hi');\n      strictEqual(await ctx.page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);\n      deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n        { resultCount: 2, resultIndex: 1 }\n      ]);\n    });\n  });\n\n  test.describe('Wrapped line search functionality', () => {\n    test('should correctly count matches across multiple wrapped lines', async () => {\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.search.onDidChangeResults(e => window.calls.push(e));\n      `);\n\n      const content = 'a'.repeat(300);\n      await ctx.proxy.write(content);\n      strictEqual(await ctx.page.evaluate(`window.search.findNext('${content}', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true);\n      deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n        { resultCount: 1, resultIndex: 0 }\n      ]);\n    });\n\n    test('should handle reverse search across wrapped lines', async () => {\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.search.onDidChangeResults(e => window.calls.push(e));\n      `);\n\n      const content = 'x'.repeat(300);\n      await ctx.proxy.write(content);\n      strictEqual(await ctx.page.evaluate(`window.search.findPrevious('${content}', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true);\n      deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n        { resultCount: 1, resultIndex: 0 }\n      ]);\n    });\n\n    test('should update counts when content changes across wrapped lines', async () => {\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.search.onDidChangeResults(e => window.calls.push(e));\n      `);\n\n      const content = 'z'.repeat(300);\n      await ctx.proxy.write(content);\n      strictEqual(await ctx.page.evaluate(`window.search.findNext('${content}', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true);\n      deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n        { resultCount: 1, resultIndex: 0 }\n      ]);\n\n      await ctx.proxy.write('\\\\n\\\\r' + content);\n      await timeout(300);\n      deepStrictEqual(await ctx.page.evaluate('window.calls'), [\n        { resultCount: 1, resultIndex: 0 },\n        { resultCount: 2, resultIndex: 0 }\n      ]);\n    });\n  });\n});\n\nfunction makeData(length: number): string {\n  let result = '';\n  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n  for (let i = 0; i < length; i++) {\n    result += characters.charAt(Math.floor(Math.random() * characters.length));\n  }\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-search/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-search/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-search/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-search/typings/addon-search.d.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-search' {\n  /**\n   * Options for a search.\n   */\n  export interface ISearchOptions {\n    /**\n     * Whether the search term is a regex.\n     */\n    regex?: boolean;\n\n    /**\n     * Whether to search for a whole word, the result is only valid if it's\n     * surrounded in \"non-word\" characters such as `_`, `(`, `)` or space.\n     */\n    wholeWord?: boolean;\n\n    /**\n     * Whether the search is case sensitive.\n     */\n    caseSensitive?: boolean;\n\n    /**\n     * Whether to do an incremental search, this will expand the selection if it\n     * still matches the term the user typed. Note that this only affects\n     * `findNext`, not `findPrevious`.\n     */\n    incremental?: boolean;\n\n    /**\n     * When set, will highlight all instances of the word on search and show\n     * them in the overview ruler if it's enabled.\n     */\n    decorations?: ISearchDecorationOptions;\n  }\n\n  /**\n   * Options for showing decorations when searching.\n   */\n  interface ISearchDecorationOptions {\n    /**\n     * The background color of a match, this must use #RRGGBB format.\n     */\n    matchBackground?: string;\n\n    /**\n     * The border color of a match.\n     */\n    matchBorder?: string;\n\n    /**\n     * The overview ruler color of a match.\n     */\n    matchOverviewRuler: string;\n\n    /**\n     * The background color for the currently active match, this must use #RRGGBB format.\n     */\n    activeMatchBackground?: string;\n\n    /**\n     * The border color of the currently active match.\n     */\n    activeMatchBorder?: string;\n\n    /**\n     * The overview ruler color of the currently active match.\n     */\n    activeMatchColorOverviewRuler: string;\n  }\n\n  /**\n   * Event data fired when search results change.\n   */\n  export interface ISearchResultChangeEvent {\n    /**\n     * The index of the currently active result, -1 when the threshold of matches is exceeded.\n     */\n    resultIndex: number;\n\n    /**\n     * The total number of search results found.\n     */\n    resultCount: number;\n  }\n\n  /**\n   * Options for the search addon.\n   */\n  export interface ISearchAddonOptions {\n    /**\n     * Max number of matches highlighted when decorations are enabled.\n     * Defaults to 1000 highlighted matches\n     */\n    highlightLimit: number\n  }\n\n  /**\n   * An xterm.js addon that provides search functionality.\n   */\n  export class SearchAddon implements ITerminalAddon {\n\n    /**\n     * Creates a new search addon.\n     * @param options Options for the search addon.\n     */\n    constructor(options?: Partial<ISearchAddonOptions>);\n\n    /**\n     * Activates the addon\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n\n    /**\n     * Search forwards for the next result that matches the search term and\n     * options.\n     * @param term The search term.\n     * @param searchOptions The options for the search.\n     */\n    public findNext(term: string, searchOptions?: ISearchOptions): boolean;\n\n    /**\n     * Search backwards for the previous result that matches the search term and\n     * options.\n     * @param term The search term.\n     * @param searchOptions The options for the search.\n     */\n    public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;\n\n    /**\n     * Clears the decorations and selection\n     */\n    public clearDecorations(): void;\n\n    /**\n     * Clears the active result decoration, this decoration is applied on top of the selection so\n     * removing it will reveal the selection underneath. This is intended to be called on the search\n     * textarea's `blur` event.\n     */\n    public clearActiveDecoration(): void;\n\n    /**\n     * Fires after a search is performed.\n     */\n    readonly onAfterSearch: IEvent<void>;\n\n    /**\n     * Fires before a search is performed.\n     */\n    readonly onBeforeSearch: IEvent<void>;\n\n    /**\n     * When decorations are enabled, fires when the search results change.\n     */\n    readonly onDidChangeResults: IEvent<ISearchResultChangeEvent>;\n  }\n}\n"
  },
  {
    "path": "addons/addon-search/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'SearchAddon';\nconst mainFile = 'addon-search.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      vs: path.resolve('../../out/vs')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-serialize/.gitignore",
    "content": "lib\nnode_modules\nout-benchmark\n"
  },
  {
    "path": "addons/addon-serialize/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-serialize/README.md",
    "content": "## @xterm/addon-serialize\n\nAn addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables xterm.js to serialize a terminal framebuffer into string or html. This addon requires xterm.js v4+.\n\n⚠️ This is an experimental addon that is still under construction ⚠️\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-serialize\n```\n\n### Usage\n\n```ts\nimport { Terminal } from \"@xterm/xterm\";\nimport { SerializeAddon } from \"@xterm/addon-serialize\";\n\nconst terminal = new Terminal();\nconst serializeAddon = new SerializeAddon();\nterminal.loadAddon(serializeAddon);\n\nterminal.write(\"something...\", () => {\n  console.log(serializeAddon.serialize());\n});\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-serialize/typings/addon-serialize.d.ts) for more advanced usage.\n\n### Benchmark\n\n⚠️ Ensure you have `lolcat`, `hexdump` programs installed in your computer\n\n```shell\n$ git clone https://github.com/xtermjs/xterm.js.git\n$ cd xterm.js\n$ npm ci\n$ cd addons/addon-serialize\n$ npm run benchmark && npm run benchmark-baseline\n$ # change some code in `@xterm/addon-serialize`\n$ npm run benchmark-eval\n```\n"
  },
  {
    "path": "addons/addon-serialize/benchmark/SerializeAddon.benchmark.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';\n\nimport { spawn } from 'node-pty';\nimport { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';\nimport { Terminal } from 'browser/public/Terminal';\nimport { SerializeAddon } from 'SerializeAddon';\n\nclass TestTerminal extends Terminal {\n  public writeSync(data: string): void {\n    (this as any)._core.writeSync(data);\n  }\n}\n\nperfContext('Terminal: sh -c \"dd if=/dev/urandom count=40 bs=1k | hexdump | lolcat -f\"', () => {\n  let content = '';\n  let contentUtf8: Uint8Array;\n\n  before(async () => {\n    const p = spawn('sh', ['-c', 'dd if=/dev/urandom count=40 bs=1k | hexdump | lolcat -f'], {\n      name: 'xterm-256color',\n      cols: 80,\n      rows: 25,\n      cwd: process.env.HOME,\n      env: process.env,\n      encoding: (null as unknown as string) // needs to be fixed in node-pty\n    });\n    const chunks: Buffer[] = [];\n    let length = 0;\n    p.onData(data => {\n      chunks.push(data as unknown as Buffer);\n      length += data.length;\n    });\n    await new Promise<void>(resolve => p.onExit(() => resolve()));\n    contentUtf8 = Buffer.concat(chunks, length);\n    // translate to content string\n    const buffer = new Uint32Array(contentUtf8.length);\n    const decoder = new Utf8ToUtf32();\n    const codepoints = decoder.decode(contentUtf8, buffer);\n    for (let i = 0; i < codepoints; ++i) {\n      content += stringFromCodePoint(buffer[i]);\n      // peek into content to force flat repr in v8\n      if (!(i % 10000000)) {\n        content[i];\n      }\n    }\n  });\n\n  perfContext('serialize', () => {\n    let terminal: TestTerminal;\n    const serializeAddon = new SerializeAddon();\n    before(() => {\n      terminal = new TestTerminal({ cols: 80, rows: 25, scrollback: 5000 });\n      serializeAddon.activate(terminal);\n      terminal.writeSync(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      return { payloadSize: serializeAddon.serialize().length };\n    }, { fork: false }).showAverageThroughput();\n  });\n});\n"
  },
  {
    "path": "addons/addon-serialize/benchmark/benchmark.json",
    "content": "{\n  \"APP_PATH\": \".benchmark\",\n  \"evalConfig\": {\n    \"tolerance\": {\n      \"*\": [0.75, 1.5],\n      \"*.dev\": [0.01, 1.5],\n      \"*.cv\": [0.01, 1.5],\n      \"EscapeSequenceParser.benchmark.js.*.averageThroughput.mean\": [0.9, 5]\n    },\n    \"skip\": [\n      \"*.median\",\n      \"*.runs\",\n      \"*.dev\",\n      \"*.cv\",\n      \"EscapeSequenceParser.benchmark.js.*.averageRuntime\",\n      \"Terminal.benchmark.js.*.averageRuntime\"\n    ]\n  } \n}\n"
  },
  {
    "path": "addons/addon-serialize/benchmark/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\n      \"dom\",\n      \"es2021\"\n    ],\n    \"rootDir\": \"..\",\n    \"outDir\": \"../out-benchmark\",\n    \"types\": [\"../../../node_modules/@types/node\"],\n    \"moduleResolution\": \"node\",\n    \"strict\": false,\n    \"target\": \"es2021\",\n    \"module\": \"commonjs\",\n    \"paths\": {\n      \"common/*\": [\"../../../src/common/*\"],\n      \"browser/*\": [\"../../../src/browser/*\"],\n      \"SerializeAddon\": [\"../src/SerializeAddon\"],\n      \"@xterm/addon-serialize\": [\n        \"../typings/addon-serialize.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\"../**/*\", \"../../../typings/xterm.d.ts\"],\n  \"exclude\": [\"../../../**/*test.ts\", \"../../**/*api.ts\"],\n  \"references\": [\n    { \"path\": \"../../../src/common\" },\n    { \"path\": \"../../../src/browser\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-serialize/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-serialize\",\n  \"version\": \"0.14.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-serialize.js\",\n  \"module\": \"lib/addon-serialize.mjs\",\n  \"types\": \"typings/addon-serialize.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\",\n    \"benchmark\": \"NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json\",\n    \"benchmark-baseline\": \"NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js\",\n    \"benchmark-eval\": \"NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-serialize/src/SerializeAddon.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport jsdom = require('jsdom');\nimport { assert } from 'chai';\nimport { SerializeAddon } from './SerializeAddon';\nimport { Terminal } from 'browser/public/Terminal';\nimport { SelectionModel } from 'browser/selection/SelectionModel';\nimport { IBufferService } from 'common/services/Services';\nimport { ThemeService } from 'browser/services/ThemeService';\n\nfunction sgr(...seq: string[]): string {\n  return `\\x1b[${seq.join(';')}m`;\n}\n\nfunction writeP(terminal: Terminal, data: string | Uint8Array): Promise<void> {\n  return new Promise(r => terminal.write(data, r));\n}\n\nclass TestSelectionService {\n  private _model: SelectionModel;\n  private _hasSelection: boolean = false;\n\n  constructor(\n    bufferService: IBufferService\n  ) {\n    this._model = new SelectionModel(bufferService);\n  }\n\n  public get model(): SelectionModel { return this._model; }\n\n  public get hasSelection(): boolean { return this._hasSelection; }\n\n  public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }\n  public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }\n\n  public setSelection(col: number, row: number, length: number): void {\n    this._model.selectionStart = [col, row];\n    this._model.selectionStartLength = length;\n    this._hasSelection = true;\n  }\n}\n\ndescribe('SerializeAddon', () => {\n  let dom: jsdom.JSDOM;\n  let window: jsdom.DOMWindow;\n\n  let serializeAddon: SerializeAddon;\n  let terminal: Terminal;\n\n  before(() => {\n    serializeAddon = new SerializeAddon();\n  });\n\n  beforeEach(() => {\n    dom = new jsdom.JSDOM('');\n    window = dom.window;\n\n    (window as any).HTMLCanvasElement.prototype.getContext = () => ({\n      createLinearGradient(): any {\n        return null;\n      },\n\n      fillRect(): void { },\n\n      getImageData(): any {\n        return { data: [0, 0, 0, 0xFF] };\n      }\n    });\n\n    terminal = new Terminal({ cols: 10, rows: 2, allowProposedApi: true });\n    terminal.loadAddon(serializeAddon);\n\n    (terminal as any)._core._themeService = new ThemeService((terminal as any)._core.optionsService);\n    (terminal as any)._core._selectionService = new TestSelectionService((terminal as any)._core._bufferService);\n  });\n\n  describe('text', () => {\n    it('restoring cursor styles', async () => {\n      await writeP(terminal, sgr('32') + '> ' + sgr('0'));\n      assert.equal(serializeAddon.serialize(), '\\u001b[32m> \\u001b[0m');\n    });\n\n    describe('ISerializeOptions.range', () => {\n      it('should serialize the top line', async () => {\n        await writeP(terminal, 'hello\\r\\nworld');\n        assert.equal(serializeAddon.serialize({\n          range: {\n            start: 0,\n            end: 0\n          }\n        }), 'hello');\n      });\n      it('should serialize multiple lines from the top', async () => {\n        await writeP(terminal, 'hello\\r\\nworld');\n        assert.equal(serializeAddon.serialize({\n          range: {\n            start: 0,\n            end: 1\n          }\n        }), 'hello\\r\\nworld');\n      });\n      it('should serialize lines in the middle', async () => {\n        await writeP(terminal, 'hello\\r\\nworld');\n        assert.equal(serializeAddon.serialize({\n          range: {\n            start: 1,\n            end: 1\n          }\n        }), 'world');\n      });\n    });\n\n    describe('underline styles', () => {\n      it('should serialize single underline with style', async () => {\n        await writeP(terminal, sgr('4:1') + 'test' + sgr('24'));\n        assert.equal(serializeAddon.serialize(), '\\u001b[4mtest\\u001b[0m');\n      });\n\n      it('should serialize double underline', async () => {\n        await writeP(terminal, sgr('4:2') + 'test' + sgr('24'));\n        assert.equal(serializeAddon.serialize(), '\\u001b[4:2mtest\\u001b[0m');\n      });\n\n      it('should serialize curly underline', async () => {\n        await writeP(terminal, sgr('4:3') + 'test' + sgr('24'));\n        assert.equal(serializeAddon.serialize(), '\\u001b[4:3mtest\\u001b[0m');\n      });\n\n      it('should serialize dotted underline', async () => {\n        await writeP(terminal, sgr('4:4') + 'test' + sgr('24'));\n        assert.equal(serializeAddon.serialize(), '\\u001b[4:4mtest\\u001b[0m');\n      });\n\n      it('should serialize dashed underline', async () => {\n        await writeP(terminal, sgr('4:5') + 'test' + sgr('24'));\n        assert.equal(serializeAddon.serialize(), '\\u001b[4:5mtest\\u001b[0m');\n      });\n\n      it('should serialize underline with RGB color', async () => {\n        await writeP(terminal, sgr('4', '58;2;255;128;64') + 'test' + sgr('24'));\n        const result = serializeAddon.serialize();\n        assert.ok(result.includes('4:1'), result);\n        assert.ok(result.includes('58:2::255:128:64'), result);\n      });\n\n      it('should serialize underline with palette color', async () => {\n        await writeP(terminal, sgr('4', '58;5;46') + 'test' + sgr('24'));\n        const result = serializeAddon.serialize();\n        assert.ok(result.includes('4:1'), result);\n        assert.ok(result.includes('58:5:46'), result);\n      });\n    });\n\n    describe('scroll region', () => {\n      let scrollTerminal: Terminal;\n      let scrollAddon: SerializeAddon;\n\n      beforeEach(() => {\n        scrollTerminal = new Terminal({ cols: 10, rows: 5, allowProposedApi: true });\n        scrollAddon = new SerializeAddon();\n        scrollTerminal.loadAddon(scrollAddon);\n      });\n\n      it('should serialize scroll region when margins are set', async () => {\n        await writeP(scrollTerminal, '\\x1b[2;4r');\n        const buffer = (scrollTerminal as any)._core.buffer;\n        assert.equal(buffer.scrollTop, 1, 'scrollTop should be 1');\n        assert.equal(buffer.scrollBottom, 3, 'scrollBottom should be 3');\n        const result = scrollAddon.serialize();\n        assert.ok(result.includes('\\x1b[2;4r'), result);\n      });\n\n      it('should not serialize scroll region when excludeModes is true', async () => {\n        await writeP(scrollTerminal, '\\x1b[2;4r');\n        const result = scrollAddon.serialize({ excludeModes: true });\n        assert.ok(!result.includes('\\x1b[2;4r'), result);\n      });\n\n      it('should restore scroll region correctly when deserialized', async () => {\n        await writeP(scrollTerminal, '\\x1b[2;4r');\n        const serialized = scrollAddon.serialize();\n        const terminal2 = new Terminal({ cols: 10, rows: 5, allowProposedApi: true });\n        terminal2.loadAddon(new SerializeAddon());\n        await writeP(terminal2, serialized);\n        const buffer = (terminal2 as any)._core.buffer;\n        assert.equal(buffer.scrollTop, 1);\n        assert.equal(buffer.scrollBottom, 3);\n      });\n    });\n\n    describe('cursor visibility', () => {\n      it('should serialize hidden cursor', async () => {\n        await writeP(terminal, 'hello\\x1b[?25l');\n        assert.equal(terminal.modes.showCursor, false);\n        const result = serializeAddon.serialize();\n        assert.ok(result.includes('\\x1b[?25l'), result);\n      });\n\n      it('should not serialize visible cursor (default state)', async () => {\n        await writeP(terminal, 'hello');\n        assert.equal(terminal.modes.showCursor, true);\n        const result = serializeAddon.serialize();\n        assert.ok(!result.includes('\\x1b[?25l'), result);\n        assert.ok(!result.includes('\\x1b[?25h'), result);\n      });\n\n      it('should not serialize cursor visibility when excludeModes is true', async () => {\n        await writeP(terminal, 'hello\\x1b[?25l');\n        const result = serializeAddon.serialize({ excludeModes: true });\n        assert.ok(!result.includes('\\x1b[?25l'), result);\n      });\n\n      it('should restore hidden cursor correctly when deserialized', async () => {\n        await writeP(terminal, 'hello\\x1b[?25l');\n        const serialized = serializeAddon.serialize();\n        const terminal2 = new Terminal({ cols: 10, rows: 2, allowProposedApi: true });\n        terminal2.loadAddon(new SerializeAddon());\n        await writeP(terminal2, serialized);\n        assert.equal(terminal2.modes.showCursor, false);\n      });\n    });\n  });\n\n  describe('html', () => {\n    it('empty terminal with selection turned off', () => {\n      const output = serializeAddon.serializeAsHTML();\n      assert.notEqual(output, '');\n      assert.equal((output.match(/<div><span> {10}<\\/span><\\/div>/g) ?? []).length, 2);\n    });\n\n    it('empty terminal with no selection', () => {\n      const output = serializeAddon.serializeAsHTML({\n        onlySelection: true\n      });\n      assert.equal(output, '');\n    });\n\n    it('basic terminal with selection', async () => {\n      await writeP(terminal, ' terminal ');\n      terminal.select(1, 0, 8);\n\n      const output = serializeAddon.serializeAsHTML({\n        onlySelection: true\n      });\n      assert.equal((output.match(/<div><span>terminal<\\/span><\\/div>/g) ?? []).length, 1, output);\n    });\n\n    it('basic terminal with html unsafe chars', async () => {\n      await writeP(terminal, ' <a>&pi; ');\n      terminal.select(1, 0, 7);\n\n      const output = serializeAddon.serializeAsHTML({\n        onlySelection: true\n      });\n      assert.equal((output.match(/<div><span>&lt;a>&amp;pi;<\\/span><\\/div>/g) ?? []).length, 1, output);\n    });\n\n    it('serializes rows within a provided range', async () => {\n      await writeP(terminal, 'bye hello\\r\\nworld');\n      const output = serializeAddon.serializeAsHTML({\n        range: {\n          startLine: 0,\n          endLine: 0,\n          startCol: 4\n        }\n      });\n      const rowMatches = output.match(/<div><span>.*?<\\/span><\\/div>/g) ?? [];\n      assert.equal(rowMatches.length, 1, output);\n      assert.ok(rowMatches[0]?.includes('hello'));\n      assert.ok(!output.includes('bye'));\n      assert.ok(!output.includes('world'));\n    });\n\n    it('cells with bold styling', async () => {\n      await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='font-weight: bold;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with italic styling', async () => {\n      await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='font-style: italic;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with inverse styling', async () => {\n      await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='color: #000000; background-color: #BFBFBF;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with underline styling', async () => {\n      await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='text-decoration: underline;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with double underline styling', async () => {\n      await writeP(terminal, ' ' + sgr('4:2') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='text-decoration: underline double;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with curly underline styling', async () => {\n      await writeP(terminal, ' ' + sgr('4:3') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='text-decoration: underline wavy;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with dotted underline styling', async () => {\n      await writeP(terminal, ' ' + sgr('4:4') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='text-decoration: underline dotted;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with dashed underline styling', async () => {\n      await writeP(terminal, ' ' + sgr('4:5') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='text-decoration: underline dashed;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with underline color (palette)', async () => {\n      await writeP(terminal, ' ' + sgr('4', '58;5;46') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.ok(output.includes('text-decoration: underline;'), output);\n      assert.ok(output.includes('text-decoration-color: #00ff00;'), output);\n    });\n\n    it('cells with underline color (RGB)', async () => {\n      await writeP(terminal, ' ' + sgr('4', '58;2;255;128;64') + 'terminal' + sgr('24') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.ok(output.includes('text-decoration: underline;'), output);\n      assert.ok(output.includes('text-decoration-color: #ff8040;'), output);\n    });\n\n    it('cells with invisible styling', async () => {\n      await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='visibility: hidden;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with dim styling', async () => {\n      await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='opacity: 0.5;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with strikethrough styling', async () => {\n      await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='text-decoration: line-through;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with combined styling', async () => {\n      await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='font-weight: bold;'> <\\/span>/g) ?? []).length, 1, output);\n      assert.equal((output.match(/<span style='font-weight: bold; text-decoration: line-through;'>termi<\\/span>/g) ?? []).length, 1, output);\n      assert.equal((output.match(/<span style='text-decoration: line-through;'>nal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with color styling', async () => {\n      await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='color: #00ff00;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with background styling', async () => {\n      await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='background-color: #00ff00;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('empty terminal with default options', async () => {\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: monospace; font-size: 15px;/g) ?? []).length, 1, output);\n    });\n\n    it('empty terminal with custom options', async () => {\n      terminal.options.fontFamily = 'verdana';\n      terminal.options.fontSize = 20;\n      terminal.options.theme = {\n        foreground: '#ff00ff',\n        background: '#00ff00'\n      };\n      const output = serializeAddon.serializeAsHTML({\n        includeGlobalBackground: true\n      });\n      assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) ?? []).length, 1, output);\n    });\n\n    it('empty terminal with background included', async () => {\n      const output = serializeAddon.serializeAsHTML({\n        includeGlobalBackground: true\n      });\n      assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: monospace; font-size: 15px;/g) ?? []).length, 1, output);\n    });\n\n    it('cells with custom color styling', async () => {\n      terminal.options.theme.black = '#ffa500';\n      terminal.options.theme = { ... terminal.options.theme };\n\n      await writeP(terminal, ' ' + sgr('38;5;0') + 'terminal' + sgr('39') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='color: #ffa500;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n\n    it('cells with color styling - xterm headless', async () => {\n      // a headless terminal doesn't have a themeservice\n      (terminal as any)._core._themeService = undefined;\n\n      await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');\n\n      const output = serializeAddon.serializeAsHTML();\n      assert.equal((output.match(/<span style='color: #00ff00;'>terminal<\\/span>/g) ?? []).length, 1, output);\n    });\n  });\n});\n"
  },
  {
    "path": "addons/addon-serialize/src/SerializeAddon.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * (EXPERIMENTAL) This Addon is still under development\n */\n\nimport type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';\nimport type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';\nimport { IAttributeData, IColor } from 'common/Types';\nimport { DEFAULT_ANSI_COLORS } from 'browser/Types';\nimport { UnderlineStyle } from 'common/buffer/Constants';\n\nfunction constrain(value: number, low: number, high: number): number {\n  return Math.max(low, Math.min(value, high));\n}\n\nfunction escapeHTMLChar(c: string): string {\n  switch (c) {\n    case '&': return '&amp;';\n    case '<': return '&lt;';\n  }\n  return c;\n}\n\n// TODO: Refine this template class later\nabstract class BaseSerializeHandler {\n  constructor(\n    protected readonly _buffer: IBuffer\n  ) {\n  }\n\n  public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {\n    // we need two of them to flip between old and new cell\n    const cell1 = this._buffer.getNullCell();\n    const cell2 = this._buffer.getNullCell();\n    let oldCell = cell1;\n\n    const startRow = range.start.y;\n    const endRow = range.end.y;\n    const startColumn = range.start.x;\n    const endColumn = range.end.x;\n\n    this._beforeSerialize(endRow - startRow, startRow, endRow);\n\n    for (let row = startRow; row <= endRow; row++) {\n      const line = this._buffer.getLine(row);\n      if (line) {\n        const startLineColumn = row === range.start.y ? startColumn : 0;\n        const endLineColumn = row === range.end.y ? endColumn: line.length;\n        for (let col = startLineColumn; col < endLineColumn; col++) {\n          const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);\n          if (!c) {\n            console.warn(`Can't get cell at row=${row}, col=${col}`);\n            continue;\n          }\n          this._nextCell(c, oldCell, row, col);\n          oldCell = c;\n        }\n      }\n      this._rowEnd(row, row === endRow);\n    }\n\n    this._afterSerialize();\n\n    return this._serializeString(excludeFinalCursorPosition);\n  }\n\n  protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }\n  protected _rowEnd(row: number, isLastRow: boolean): void { }\n  protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { }\n  protected _afterSerialize(): void { }\n  protected _serializeString(excludeFinalCursorPosition?: boolean): string { return ''; }\n}\n\nfunction equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {\n  return cell1.getFgColorMode() === cell2.getFgColorMode()\n    && cell1.getFgColor() === cell2.getFgColor();\n}\n\nfunction equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {\n  return cell1.getBgColorMode() === cell2.getBgColorMode()\n    && cell1.getBgColor() === cell2.getBgColor();\n}\n\nfunction equalUnderline(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {\n  // If neither cell has underline, consider them equal regardless of internal underline color\n  // values\n  if (!cell1.isUnderline() && !cell2.isUnderline()) {\n    return true;\n  }\n  if (cell1.getUnderlineStyle() !== cell2.getUnderlineStyle()) {\n    return false;\n  }\n  const cell1Default = cell1.isUnderlineColorDefault();\n  const cell2Default = cell2.isUnderlineColorDefault();\n  if (cell1Default && cell2Default) {\n    return true;\n  }\n  if (cell1Default !== cell2Default) {\n    return false;\n  }\n  return cell1.getUnderlineColor() === cell2.getUnderlineColor()\n    && cell1.getUnderlineColorMode() === cell2.getUnderlineColorMode();\n}\n\nfunction equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {\n  return cell1.isInverse() === cell2.isInverse()\n    && cell1.isBold() === cell2.isBold()\n    && cell1.isUnderline() === cell2.isUnderline()\n    && equalUnderline(cell1, cell2)\n    && cell1.isOverline() === cell2.isOverline()\n    && cell1.isBlink() === cell2.isBlink()\n    && cell1.isInvisible() === cell2.isInvisible()\n    && cell1.isItalic() === cell2.isItalic()\n    && cell1.isDim() === cell2.isDim()\n    && cell1.isStrikethrough() === cell2.isStrikethrough();\n}\n\nfunction attributesEquals(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {\n  const cell1AsBufferCell = cell1 as IBufferCell;\n  if (typeof cell1AsBufferCell.attributesEquals === 'function') {\n    return cell1AsBufferCell.attributesEquals(cell2);\n  }\n  return equalFg(cell1, cell2)\n    && equalBg(cell1, cell2)\n    && equalFlags(cell1, cell2);\n}\n\nclass StringSerializeHandler extends BaseSerializeHandler {\n  private _rowIndex: number = 0;\n  private _allRows: string[] = new Array<string>();\n  private _allRowSeparators: string[] = new Array<string>();\n  private _currentRow: string = '';\n  private _nullCellCount: number = 0;\n\n  // we can see a full colored cell and a null cell that only have background the same style\n  // but the information isn't preserved by null cell itself\n  // so wee need to record it when required.\n  private _cursorStyle: IBufferCell = this._buffer.getNullCell();\n\n  // where exact the cursor styles comes from\n  // because we can't copy the cell directly\n  // so we remember where the content comes from instead\n  private _cursorStyleRow: number = 0;\n  private _cursorStyleCol: number = 0;\n\n  // this is a null cell for reference for checking whether background is empty or not\n  private _backgroundCell: IBufferCell = this._buffer.getNullCell();\n\n  private _firstRow: number = 0;\n  private _lastCursorRow: number = 0;\n  private _lastCursorCol: number = 0;\n  private _lastContentCursorRow: number = 0;\n  private _lastContentCursorCol: number = 0;\n\n  constructor(\n    buffer: IBuffer,\n    private readonly _terminal: Terminal\n  ) {\n    super(buffer);\n  }\n\n  protected _beforeSerialize(rows: number, start: number, end: number): void {\n    this._allRows = new Array<string>(rows);\n    this._lastContentCursorRow = start;\n    this._lastCursorRow = start;\n    this._firstRow = start;\n  }\n\n  private _thisRowLastChar: IBufferCell = this._buffer.getNullCell();\n  private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();\n  private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();\n  protected _rowEnd(row: number, isLastRow: boolean): void {\n    // if there is colorful empty cell at line end, whe must pad it back, or the the color block\n    // will missing\n    if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {\n      // use clear right to set background.\n      this._currentRow += `\\u001b[${this._nullCellCount}X`;\n    }\n\n    let rowSeparator = '';\n\n    // handle row separator\n    if (!isLastRow) {\n      // Enable BCE\n      if (row - this._firstRow >= this._terminal.rows) {\n        this._buffer.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell);\n      }\n\n      // Fetch current line\n      const currentLine = this._buffer.getLine(row)!;\n      // Fetch next line\n      const nextLine = this._buffer.getLine(row + 1)!;\n\n      if (!nextLine.isWrapped) {\n        // just insert the line break\n        rowSeparator = '\\r\\n';\n        // we sended the enter\n        this._lastCursorRow = row + 1;\n        this._lastCursorCol = 0;\n      } else {\n        rowSeparator = '';\n        const thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar)!;\n        const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar)!;\n        const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar)!;\n        const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1;\n\n        // validate whether this line wrap is ever possible\n        // which mean whether cursor can placed at a overflow position (x === row) naturally\n        let isValid = false;\n\n        if (\n          // you must output character to cause overflow, control sequence can't do this\n          nextRowFirstChar.getChars() &&\n            isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0\n        ) {\n          if (\n            // the last character can't be null,\n            // you can't use control sequence to move cursor to (x === row)\n            (thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) &&\n            // change background of the first wrapped cell also affects BCE\n            // so we mark it as invalid to simply the process to determine line separator\n            equalBg(thisRowLastChar, nextRowFirstChar)\n          ) {\n            isValid = true;\n          }\n\n          if (\n            // the second to last character can't be null if the next line starts with CJK,\n            // you can't use control sequence to move cursor to (x === row)\n            isNextRowFirstCharDoubleWidth &&\n            (thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) &&\n            // change background of the first wrapped cell also affects BCE\n            // so we mark it as invalid to simply the process to determine line separator\n            equalBg(thisRowLastChar, nextRowFirstChar) &&\n            equalBg(thisRowLastSecondChar, nextRowFirstChar)\n          ) {\n            isValid = true;\n          }\n        }\n\n        if (!isValid) {\n          // force the wrap with magic\n          // insert enough character to force the wrap\n          rowSeparator = '-'.repeat(this._nullCellCount + 1);\n          // move back and erase next line head\n          rowSeparator += '\\u001b[1D\\u001b[1X';\n\n          if (this._nullCellCount > 0) {\n            // do these because we filled the last several null slot, which we shouldn't\n            rowSeparator += '\\u001b[A';\n            rowSeparator += `\\u001b[${currentLine.length - this._nullCellCount}C`;\n            rowSeparator += `\\u001b[${this._nullCellCount}X`;\n            rowSeparator += `\\u001b[${currentLine.length - this._nullCellCount}D`;\n            rowSeparator += '\\u001b[B';\n          }\n\n          // This is content and need the be serialized even it is invisible.\n          // without this, wrap will be missing from outputs.\n          this._lastContentCursorRow = row + 1;\n          this._lastContentCursorCol = 0;\n\n          // force commit the cursor position\n          this._lastCursorRow = row + 1;\n          this._lastCursorCol = 0;\n        }\n      }\n    }\n\n    this._allRows[this._rowIndex] = this._currentRow;\n    this._allRowSeparators[this._rowIndex++] = rowSeparator;\n    this._currentRow = '';\n    this._nullCellCount = 0;\n  }\n\n  private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] {\n    const sgrSeq: number[] = [];\n    if (attributesEquals(cell, oldCell)) {\n      return sgrSeq;\n    }\n    const fgChanged = !equalFg(cell, oldCell);\n    const bgChanged = !equalBg(cell, oldCell);\n    const flagsChanged = !equalFlags(cell, oldCell);\n\n    if (fgChanged || bgChanged || flagsChanged) {\n      if (cell.isAttributeDefault()) {\n        if (!oldCell.isAttributeDefault()) {\n          sgrSeq.push(0);\n        }\n      } else {\n        if (fgChanged) {\n          const color = cell.getFgColor();\n          if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }\n          else if (cell.isFgPalette()) {\n            if (color >= 16) { sgrSeq.push(38, 5, color); }\n            else { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }\n          }\n          else { sgrSeq.push(39); }\n        }\n        if (bgChanged) {\n          const color = cell.getBgColor();\n          if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }\n          else if (cell.isBgPalette()) {\n            if (color >= 16) { sgrSeq.push(48, 5, color); }\n            else { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }\n          }\n          else { sgrSeq.push(49); }\n        }\n        if (flagsChanged) {\n          if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }\n          if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }\n          if (!equalUnderline(cell, oldCell)) {\n            const style = cell.getUnderlineStyle();\n            if (style === UnderlineStyle.NONE) {\n              sgrSeq.push(24);\n            } else if (style === UnderlineStyle.SINGLE && cell.isUnderlineColorDefault()) {\n              sgrSeq.push(4);\n            } else {\n              // Use SGR 4:X format for underline styles\n              sgrSeq.push('4:' + style as unknown as number);\n              // Handle underline color\n              if (!cell.isUnderlineColorDefault()) {\n                const color = cell.getUnderlineColor();\n                if (cell.isUnderlineColorRGB()) {\n                  sgrSeq.push('58:2::' + ((color >>> 16) & 0xFF) + ':' + ((color >>> 8) & 0xFF) + ':' + (color & 0xFF) as unknown as number);\n                } else {\n                  sgrSeq.push('58:5:' + color as unknown as number);\n                }\n              }\n            }\n          } else if (cell.isUnderline() !== oldCell.isUnderline()) {\n            sgrSeq.push(cell.isUnderline() ? 4 : 24);\n          }\n          if (cell.isOverline() !== oldCell.isOverline()) { sgrSeq.push(cell.isOverline() ? 53 : 55); }\n          if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }\n          if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }\n          if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }\n          if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }\n          if (cell.isStrikethrough() !== oldCell.isStrikethrough()) { sgrSeq.push(cell.isStrikethrough() ? 9 : 29); }\n        }\n      }\n    }\n\n    return sgrSeq;\n  }\n\n  protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {\n    // a width 0 cell don't need to be count because it is just a placeholder after a CJK character;\n    const isPlaceHolderCell = cell.getWidth() === 0;\n\n    if (isPlaceHolderCell) {\n      return;\n    }\n\n    // this cell don't have content\n    const isEmptyCell = cell.getChars() === '';\n\n    const sgrSeq = this._diffStyle(cell, this._cursorStyle);\n\n    // the empty cell style is only assumed to be changed when background changed, because\n    // foreground is always 0.\n    const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0;\n\n    /**\n     *  handles style change\n     */\n    if (styleChanged) {\n      // before update the style, we need to fill empty cell back\n      if (this._nullCellCount > 0) {\n        // use clear right to set background.\n        if (!equalBg(this._cursorStyle, this._backgroundCell)) {\n          this._currentRow += `\\u001b[${this._nullCellCount}X`;\n        }\n        // use move right to move cursor.\n        this._currentRow += `\\u001b[${this._nullCellCount}C`;\n        this._nullCellCount = 0;\n      }\n\n      this._lastContentCursorRow = this._lastCursorRow = row;\n      this._lastContentCursorCol = this._lastCursorCol = col;\n\n      this._currentRow += `\\u001b[${sgrSeq.join(';')}m`;\n\n      // update the last cursor style\n      const line = this._buffer.getLine(row);\n      if (line !== undefined) {\n        line.getCell(col, this._cursorStyle);\n        this._cursorStyleRow = row;\n        this._cursorStyleCol = col;\n      }\n    }\n\n    /**\n     *  handles actual content\n     */\n    if (isEmptyCell) {\n      this._nullCellCount += cell.getWidth();\n    } else {\n      if (this._nullCellCount > 0) {\n        // we can just assume we have same style with previous one here\n        // because style change is handled by previous stage\n        // use move right when background is empty, use clear right when there is background.\n        if (equalBg(this._cursorStyle, this._backgroundCell)) {\n          this._currentRow += `\\u001b[${this._nullCellCount}C`;\n        } else {\n          this._currentRow += `\\u001b[${this._nullCellCount}X`;\n          this._currentRow += `\\u001b[${this._nullCellCount}C`;\n        }\n        this._nullCellCount = 0;\n      }\n\n      this._currentRow += cell.getChars();\n\n      // update cursor\n      this._lastContentCursorRow = this._lastCursorRow = row;\n      this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth();\n    }\n  }\n\n  protected _serializeString(excludeFinalCursorPosition: boolean): string {\n    let rowEnd = this._allRows.length;\n\n    // the fixup is only required for data without scrollback\n    // because it will always be placed at last line otherwise\n    if (this._buffer.length - this._firstRow <= this._terminal.rows) {\n      rowEnd = this._lastContentCursorRow + 1 - this._firstRow;\n      this._lastCursorCol = this._lastContentCursorCol;\n      this._lastCursorRow = this._lastContentCursorRow;\n    }\n\n    let content = '';\n\n    for (let i = 0; i < rowEnd; i++) {\n      content += this._allRows[i];\n      if (i + 1 < rowEnd) {\n        content += this._allRowSeparators[i];\n      }\n    }\n\n    // restore the cursor\n    if (!excludeFinalCursorPosition) {\n      const realCursorRow = this._buffer.baseY + this._buffer.cursorY;\n      const realCursorCol = this._buffer.cursorX;\n\n      const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol);\n\n      const moveRight = (offset: number): void => {\n        if (offset > 0) {\n          content += `\\u001b[${offset}C`;\n        } else if (offset < 0) {\n          content += `\\u001b[${-offset}D`;\n        }\n      };\n      const moveDown = (offset: number): void => {\n        if (offset > 0) {\n          content += `\\u001b[${offset}B`;\n        } else if (offset < 0) {\n          content += `\\u001b[${-offset}A`;\n        }\n      };\n\n      if (cursorMoved) {\n        moveDown(realCursorRow - this._lastCursorRow);\n        moveRight(realCursorCol - this._lastCursorCol);\n      }\n    }\n\n    // Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677\n    // HACK: Internal API access since it's awkward to expose this in the API and serialize will\n    // likely be the only consumer\n    const curAttrData: IAttributeData = (this._terminal as any)._core._inputHandler._curAttrData;\n    const sgrSeq = this._diffStyle(curAttrData, this._cursorStyle);\n    if (sgrSeq.length > 0) {\n      content += `\\u001b[${sgrSeq.join(';')}m`;\n    }\n\n    return content;\n  }\n}\n\nexport class SerializeAddon implements ITerminalAddon, ISerializeApi {\n  private _terminal: Terminal | undefined;\n\n  public activate(terminal: Terminal): void {\n    this._terminal = terminal;\n  }\n\n  private _serializeBufferByScrollback(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {\n    const maxRows = buffer.length;\n    const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);\n    return this._serializeBufferByRange(terminal, buffer, {\n      start: maxRows - correctRows,\n      end: maxRows - 1\n    }, false);\n  }\n\n  private _serializeBufferByRange(terminal: Terminal, buffer: IBuffer, range: ISerializeRange, excludeFinalCursorPosition: boolean): string {\n    const handler = new StringSerializeHandler(buffer, terminal);\n    return handler.serialize({\n      start: { x: 0,             y: typeof range.start === 'number' ? range.start : range.start.line },\n      end:   { x: terminal.cols, y: typeof range.end   === 'number' ? range.end   : range.end.line   }\n    }, excludeFinalCursorPosition);\n  }\n\n  private _serializeBufferAsHTML(terminal: Terminal, options: Partial<IHTMLSerializeOptions>): string {\n    const buffer = terminal.buffer.active;\n    const handler = new HTMLSerializeHandler(buffer, terminal, options);\n    const onlySelection = options.onlySelection ?? false;\n    const range = options.range;\n    if (range) {\n      return handler.serialize({\n        start: { x: range.startCol,             y: typeof range.startLine === 'number' ? range.startLine : range.startLine },\n        end:   { x: terminal.cols, y: typeof range.endLine   === 'number' ? range.endLine   : range.endLine   }\n      });\n    }\n    if (!onlySelection) {\n      const maxRows = buffer.length;\n      const scrollback = options.scrollback;\n      const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);\n      return handler.serialize({\n        start: { x: 0,             y: maxRows - correctRows },\n        end:   { x: terminal.cols, y: maxRows - 1           }\n      });\n    }\n\n    const selection = this._terminal?.getSelectionPosition();\n    if (selection !== undefined) {\n      return handler.serialize({\n        start: { x: selection.start.x, y: selection.start.y },\n        end:   { x: selection.end.x,   y: selection.end.y   }\n      });\n    }\n\n    return '';\n  }\n\n  /**\n   * Serializes the scroll region (DECSTBM) if it's not set to the full terminal size.\n   * Uses internal API access since scroll region is not exposed in the public API.\n   */\n  private _serializeScrollRegion(terminal: Terminal): string {\n    // HACK: Internal API access since scroll region is not exposed in the public API\n    const buffer = (terminal as any)._core.buffer;\n    const scrollTop: number = buffer.scrollTop;\n    const scrollBottom: number = buffer.scrollBottom;\n\n    // Only serialize if scroll region is not the default (full terminal size)\n    if (scrollTop !== 0 || scrollBottom !== terminal.rows - 1) {\n      // DECSTBM uses 1-based indices: CSI Ps ; Ps r\n      return `\\x1b[${scrollTop + 1};${scrollBottom + 1}r`;\n    }\n\n    return '';\n  }\n\n  private _serializeModes(terminal: Terminal): string {\n    let content = '';\n    const modes = terminal.modes;\n\n    // Default: false\n    if (modes.applicationCursorKeysMode) content += '\\x1b[?1h';\n    if (modes.applicationKeypadMode) content += '\\x1b[?66h';\n    if (modes.bracketedPasteMode) content += '\\x1b[?2004h';\n    if (modes.insertMode) content += '\\x1b[4h';\n    if (modes.originMode) content += '\\x1b[?6h';\n    if (modes.reverseWraparoundMode) content += '\\x1b[?45h';\n    if (modes.sendFocusMode) content += '\\x1b[?1004h';\n    // synchronizedOutputMode doesn't need to be serialized as it's a temporary mode\n\n    // Default: true\n    if (modes.wraparoundMode === false) content += '\\x1b[?7l';\n\n    // Default: 'none'\n    if (modes.mouseTrackingMode !== 'none') {\n      switch (modes.mouseTrackingMode) {\n        case 'x10': content += '\\x1b[?9h'; break;\n        case 'vt200': content += '\\x1b[?1000h'; break;\n        case 'drag': content += '\\x1b[?1002h'; break;\n        case 'any': content += '\\x1b[?1003h'; break;\n      }\n    }\n\n    // Cursor visibility (DECTCEM)\n    // Default: visible\n    if (!modes.showCursor) {\n      content += '\\x1b[?25l';\n    }\n\n    return content;\n  }\n\n  public serialize(options?: ISerializeOptions): string {\n    // TODO: Add combinedData support\n    if (!this._terminal) {\n      throw new Error('Cannot use addon until it has been loaded');\n    }\n\n    // Normal buffer\n    let content = options?.range\n      ? this._serializeBufferByRange(this._terminal, this._terminal.buffer.normal, options.range, true)\n      : this._serializeBufferByScrollback(this._terminal, this._terminal.buffer.normal, options?.scrollback);\n\n    // Alternate buffer\n    if (!options?.excludeAltBuffer) {\n      if (this._terminal.buffer.active.type === 'alternate') {\n        const alternativeScreenContent = this._serializeBufferByScrollback(this._terminal, this._terminal.buffer.alternate, undefined);\n        content += `\\u001b[?1049h\\u001b[H${alternativeScreenContent}`;\n      }\n    }\n\n    // Modes and scroll region\n    if (!options?.excludeModes) {\n      content += this._serializeModes(this._terminal);\n      content += this._serializeScrollRegion(this._terminal);\n    }\n\n    return content;\n  }\n\n  public serializeAsHTML(options?: Partial<IHTMLSerializeOptions>): string {\n    if (!this._terminal) {\n      throw new Error('Cannot use addon until it has been loaded');\n    }\n\n    return this._serializeBufferAsHTML(this._terminal, options ?? {});\n  }\n\n  public dispose(): void { }\n}\n\nexport class HTMLSerializeHandler extends BaseSerializeHandler {\n  private _currentRow: string = '';\n\n  private _htmlContent = '';\n\n  private _ansiColors: Readonly<IColor[]>;\n\n  constructor(\n    buffer: IBuffer,\n    private readonly _terminal: Terminal,\n    private readonly _options: Partial<IHTMLSerializeOptions>\n  ) {\n    super(buffer);\n\n    // For xterm headless: fallback to ansi colors\n    if ((_terminal as any)._core._themeService) {\n      this._ansiColors = (_terminal as any)._core._themeService.colors.ansi;\n    }\n    else {\n      this._ansiColors = DEFAULT_ANSI_COLORS;\n    }\n  }\n\n  protected _beforeSerialize(rows: number, start: number, end: number): void {\n    this._htmlContent += '<html><body><!--StartFragment--><pre>';\n\n    let foreground = '#000000';\n    let background = '#ffffff';\n    if (this._options.includeGlobalBackground ?? false) {\n      foreground = this._terminal.options.theme?.foreground ?? '#ffffff';\n      background = this._terminal.options.theme?.background ?? '#000000';\n    }\n\n    const globalStyleDefinitions = [];\n    globalStyleDefinitions.push('color: ' + foreground + ';');\n    globalStyleDefinitions.push('background-color: ' + background + ';');\n    globalStyleDefinitions.push('font-family: ' + this._terminal.options.fontFamily + ';');\n    globalStyleDefinitions.push('font-size: ' + this._terminal.options.fontSize + 'px;');\n    this._htmlContent += '<div style=\\'' + globalStyleDefinitions.join(' ') + '\\'>';\n  }\n\n  protected _afterSerialize(): void {\n    this._htmlContent += '</div>';\n    this._htmlContent += '</pre><!--EndFragment--></body></html>';\n  }\n\n  protected _rowEnd(row: number, isLastRow: boolean): void {\n    this._htmlContent += '<div><span>' + this._currentRow + '</span></div>';\n    this._currentRow = '';\n  }\n\n  private _getHexColor(cell: IBufferCell, isFg: boolean): string | undefined {\n    const color = isFg ? cell.getFgColor() : cell.getBgColor();\n    if (isFg ? cell.isFgRGB() : cell.isBgRGB()) {\n      const rgb = [\n        (color >> 16) & 255,\n        (color >>  8) & 255,\n        (color      ) & 255\n      ];\n      return '#' + rgb.map(x => x.toString(16).padStart(2, '0')).join('');\n    }\n    if (isFg ? cell.isFgPalette() : cell.isBgPalette()) {\n      return this._ansiColors[color].css;\n    }\n    return undefined;\n  }\n\n  private _getUnderlineColor(cell: IBufferCell): string | undefined {\n    if (cell.isUnderlineColorDefault()) {\n      return undefined;\n    }\n    const color = cell.getUnderlineColor();\n    if (cell.isUnderlineColorRGB()) {\n      const rgb = [\n        (color >> 16) & 255,\n        (color >>  8) & 255,\n        (color      ) & 255\n      ];\n      return '#' + rgb.map(x => x.toString(16).padStart(2, '0')).join('');\n    }\n    // Palette color\n    return this._ansiColors[color].css;\n  }\n\n  private _getUnderlineStyle(cell: IBufferCell): string {\n    switch (cell.getUnderlineStyle()) {\n      case UnderlineStyle.SINGLE:\n        return 'underline';\n      case UnderlineStyle.DOUBLE:\n        return 'underline double';\n      case UnderlineStyle.CURLY:\n        return 'underline wavy';\n      case UnderlineStyle.DOTTED:\n        return 'underline dotted';\n      case UnderlineStyle.DASHED:\n        return 'underline dashed';\n      default:\n        return 'underline';\n    }\n  }\n\n  private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): string[] | undefined {\n    const content: string[] = [];\n\n    if (attributesEquals(cell, oldCell)) {\n      return undefined;\n    }\n\n    const fgChanged = !equalFg(cell, oldCell);\n    const bgChanged = !equalBg(cell, oldCell);\n    const flagsChanged = !equalFlags(cell, oldCell);\n\n    if (fgChanged || bgChanged || flagsChanged) {\n      const fgHexColor = this._getHexColor(cell, true);\n      if (fgHexColor) {\n        content.push('color: ' + fgHexColor + ';');\n      }\n\n      const bgHexColor = this._getHexColor(cell, false);\n      if (bgHexColor) {\n        content.push('background-color: ' + bgHexColor + ';');\n      }\n\n      if (cell.isInverse()) { content.push('color: #000000; background-color: #BFBFBF;'); }\n      if (cell.isBold()) { content.push('font-weight: bold;'); }\n\n      // Handle text-decoration (underline, overline, strikethrough, blink)\n      const decorations: string[] = [];\n      if (cell.isUnderline()) {\n        decorations.push(this._getUnderlineStyle(cell));\n      }\n      if (cell.isOverline()) {\n        decorations.push('overline');\n      }\n      if (cell.isStrikethrough()) {\n        decorations.push('line-through');\n      }\n      if (cell.isBlink()) {\n        decorations.push('blink');\n      }\n      if (decorations.length > 0) {\n        content.push('text-decoration: ' + decorations.join(' ') + ';');\n      }\n\n      // Handle underline color\n      if (cell.isUnderline()) {\n        const underlineColor = this._getUnderlineColor(cell);\n        if (underlineColor) {\n          content.push('text-decoration-color: ' + underlineColor + ';');\n        }\n      }\n\n      if (cell.isInvisible()) { content.push('visibility: hidden;'); }\n      if (cell.isItalic()) { content.push('font-style: italic;'); }\n      if (cell.isDim()) { content.push('opacity: 0.5;'); }\n\n      return content;\n    }\n\n    return undefined;\n  }\n\n  protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {\n    // a width 0 cell don't need to be count because it is just a placeholder after a CJK character;\n    const isPlaceHolderCell = cell.getWidth() === 0;\n    if (isPlaceHolderCell) {\n      return;\n    }\n\n    // this cell don't have content\n    const isEmptyCell = cell.getChars() === '';\n\n    const styleDefinitions = this._diffStyle(cell, oldCell);\n\n    // handles style change\n    if (styleDefinitions) {\n      this._currentRow += styleDefinitions.length === 0 ?\n        '</span><span>' :\n        '</span><span style=\\'' + styleDefinitions.join(' ') + '\\'>';\n    }\n\n    // handles actual content\n    if (isEmptyCell) {\n      this._currentRow += ' ';\n    } else {\n      this._currentRow += escapeHTMLChar(cell.getChars());\n    }\n  }\n\n  protected _serializeString(): string {\n    return this._htmlContent;\n  }\n}\n"
  },
  {
    "path": "addons/addon-serialize/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"@xterm/addon-serialize\": [\n        \"../typings/addon-serialize.d.ts\"\n      ]\n    },\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-serialize/test/SerializeAddon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepStrictEqual, notDeepStrictEqual, strictEqual } from 'assert';\nimport { readFile } from 'fs';\nimport { resolve } from 'path';\nimport { ITestContext, createTestContext, openTerminal, timeout, writeSync } from '../../../test/playwright/TestUtils';\n\nconst writeRawSync = (page: any, str: string): Promise<void> => writeSync(ctx.page, `' +` + JSON.stringify(str) + `+ '`);\n\nconst testNormalScreenEqual = async (page: any, str: string): Promise<void> => {\n  await writeRawSync(ctx.page, str);\n  const originalBuffer = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);\n\n  const result = await ctx.page.evaluate(`window.serialize.serialize();`) as string;\n  await ctx.page.evaluate(`term.reset();`);\n  await writeRawSync(ctx.page, result);\n  const newBuffer = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);\n\n  deepStrictEqual(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));\n};\n\nasync function testSerializeEquals(writeContent: string, expectedSerialized: string): Promise<void> {\n  await writeRawSync(ctx.page, writeContent);\n  const result = await ctx.page.evaluate(`window.serialize.serialize();`) as string;\n  strictEqual(result, expectedSerialized);\n}\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx, { rows: 10, cols: 10 });\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('SerializeAddon', () => {\n\n  test.beforeEach(async () => {\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.serialize?.dispose();\n      window.serialize = new SerializeAddon();\n      window.term.loadAddon(window.serialize);\n      window.inspectBuffer = (buffer) => {\n        const lines = [];\n        for (let i = 0; i < buffer.length; i++) {\n          // Do this intentionally to get content of underlining source\n          const bufferLine = buffer.getLine(i)._line;\n          lines.push(JSON.stringify(bufferLine));\n        }\n        return {\n          x: buffer.cursorX,\n          y: buffer.cursorY,\n          data: lines\n        };\n      }\n    `);\n  });\n\n  test.beforeEach(async () => {\n    await ctx.proxy.reset();\n  });\n\n  test('produce different output when we call test util with different text', async function(): Promise<any> {\n    await writeRawSync(ctx.page, '12345');\n    const buffer1 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);\n\n    await ctx.page.evaluate(`term.reset();`);\n    await writeRawSync(ctx.page, '67890');\n    const buffer2 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);\n\n    notDeepStrictEqual(JSON.stringify(buffer1), JSON.stringify(buffer2));\n  });\n\n  test('produce different output when we call test util with different line wrap', async function(): Promise<any> {\n    await writeRawSync(ctx.page, '1234567890\\r\\n12345');\n    const buffer3 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);\n\n    await ctx.page.evaluate(`term.reset();`);\n    await writeRawSync(ctx.page, '123456789012345');\n    const buffer4 = await ctx.page.evaluate(`inspectBuffer(term.buffer.normal);`);\n\n    notDeepStrictEqual(JSON.stringify(buffer3), JSON.stringify(buffer4));\n  });\n\n  test('empty content', async function(): Promise<any> {\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), '');\n  });\n\n  test('unwrap wrapped line', async function(): Promise<any> {\n    const lines = ['123456789123456789'];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('does not unwrap non-wrapped line', async function(): Promise<any> {\n    const lines = [\n      '123456789',\n      '123456789'\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n\n  test('preserve last empty lines', async function(): Promise<any> {\n    const cols = 10;\n    const lines = [\n      '',\n      '',\n      digitsString(cols),\n      digitsString(cols),\n      '',\n      '',\n      digitsString(cols),\n      digitsString(cols),\n      '',\n      '',\n      ''\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('digits content', async function(): Promise<any> {\n    const rows = 10;\n    const cols = 10;\n    const digitsLine = digitsString(cols);\n    const lines = newArray<string>(digitsLine, rows);\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize with half of scrollback', async function(): Promise<any> {\n    const rows = 20;\n    const scrollback = rows - 10;\n    const halfScrollback = scrollback / 2;\n    const cols = 10;\n    const lines = newArray<string>((index: number) => digitsString(cols, index), rows);\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\\r\\n'));\n  });\n\n  test('serialize 0 rows of scrollback', async function(): Promise<any> {\n    const rows = 20;\n    const cols = 10;\n    const lines = newArray<string>((index: number) => digitsString(cols, index), rows);\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\\r\\n'));\n  });\n\n  test('serialize exclude modes', async () => {\n    await ctx.proxy.write('before\\x1b[?1hafter');\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), 'beforeafter\\x1b[?1h');\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ excludeModes: true });`), 'beforeafter');\n  });\n\n  test('serialize exclude alt buffer', async () => {\n    await ctx.proxy.write('normal\\x1b[?1049h\\x1b[Halt');\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), 'normal\\x1b[?1049h\\x1b[Halt');\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize({ excludeAltBuffer: true });`), 'normal');\n  });\n\n  test('serialize all rows of content with color16', async function(): Promise<any> {\n    const cols = 10;\n    const color16 = [\n      30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color\n      90, 91, 92, 93, 94, 95, 96, 97,\n      40, 41, 42, 43, 44, 45, 46, 47, // Set background color\n      100, 101, 103, 104, 105, 106, 107\n    ];\n    const rows = color16.length;\n    const lines = newArray<string>(\n      (index: number) => digitsString(cols, index, `\\x1b[${color16[index % color16.length]}m`),\n      rows\n    );\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with fg/bg flags', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_P16_GREEN) + line,  // Workaround: If we clear all flags a the end, serialize will use \\x1b[0m to clear instead of the sepcific disable sequence\n      sgr(INVERSE) + line,\n      sgr(BOLD) + line,\n      sgr(UNDERLINED) + line,\n      sgr(BLINK) + line,\n      sgr(INVISIBLE) + line,\n      sgr(STRIKETHROUGH) + line,\n      sgr(NO_INVERSE) + line,\n      sgr(NO_BOLD) + line,\n      sgr(NO_UNDERLINED) + line,\n      sgr(NO_BLINK) + line,\n      sgr(NO_INVISIBLE) + line,\n      sgr(NO_STRIKETHROUGH) + line\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('buffer cell attributesEquals compares underline style and color', async () => {\n    await ctx.proxy.write(`${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}A${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}B${sgr(NORMAL)}`);\n    const sameAttributes = await ctx.page.evaluate(`(() => {\n      const line = window.term.buffer.active.getLine(0);\n      const cellA = line?.getCell(0);\n      const cellB = line?.getCell(1);\n      if (!cellA || !cellB) {\n        return undefined;\n      }\n      return cellA.attributesEquals(cellB);\n    })()`);\n    strictEqual(sameAttributes, true);\n\n    await ctx.page.evaluate(`window.term.reset()`);\n    await ctx.proxy.write(`${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}A${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_GREEN)}B${sgr(NORMAL)}`);\n    const differentColor = await ctx.page.evaluate(`(() => {\n      const line = window.term.buffer.active.getLine(0);\n      const cellA = line?.getCell(0);\n      const cellB = line?.getCell(1);\n      if (!cellA || !cellB) {\n        return undefined;\n      }\n      return cellA.attributesEquals(cellB);\n    })()`);\n    strictEqual(differentColor, false);\n\n    await ctx.page.evaluate(`window.term.reset()`);\n    await ctx.proxy.write(`${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}A${sgr(UNDERLINED, UNDERLINE_COLOR_RED)}B${sgr(NORMAL)}`);\n    const differentStyle = await ctx.page.evaluate(`(() => {\n      const line = window.term.buffer.active.getLine(0);\n      const cellA = line?.getCell(0);\n      const cellB = line?.getCell(1);\n      if (!cellA || !cellB) {\n        return undefined;\n      }\n      return cellA.attributesEquals(cellB);\n    })()`);\n    strictEqual(differentStyle, false);\n  });\n\n  test('serialize all rows of content with color256', async function(): Promise<any> {\n    const rows = 32;\n    const cols = 10;\n    const lines = newArray<string>(\n      (index: number) => digitsString(cols, index, `\\x1b[38;5;${16 + index}m`),\n      rows\n    );\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with overline', async () => {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(OVERLINED) + line,                   // Overlined\n      sgr(UNDERLINED) + line,                  // Overlined, Underlined\n      sgr(NORMAL) + line                       // Normal\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with color16 and style separately', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_P16_RED) + line,     // fg Red,\n      sgr(UNDERLINED) + line,     // fg Red, Underlined\n      sgr(FG_P16_GREEN) + line,   // fg Green, Underlined\n      sgr(INVERSE) + line,        // fg Green, Underlined, Inverse\n      sgr(NO_INVERSE) + line,     // fg Green, Underlined\n      sgr(INVERSE) + line,        // fg Green, Underlined, Inverse\n      sgr(BG_P16_YELLOW) + line,  // fg Green, bg Yellow, Underlined, Inverse\n      sgr(FG_RESET) + line,       // bg Yellow, Underlined, Inverse\n      sgr(BG_RESET) + line,       // Underlined, Inverse\n      sgr(NORMAL) + line          // Back to normal\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with color16 and style together', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_P16_RED) + line,                   // fg Red\n      sgr(FG_P16_GREEN, BG_P16_YELLOW) + line,  // fg Green, bg Yellow\n      sgr(UNDERLINED, ITALIC) + line,           // fg Green, bg Yellow, Underlined, Italic\n      sgr(NO_UNDERLINED, NO_ITALIC) + line,     // fg Green, bg Yellow\n      sgr(FG_RESET, ITALIC) + line,             // bg Yellow, Italic\n      sgr(BG_RESET) + line,                     // Italic\n      sgr(NORMAL) + line,                       // Back to normal\n      sgr(FG_P16_RED) + line,                   // fg Red\n      sgr(FG_P16_GREEN, BG_P16_YELLOW) + line,  // fg Green, bg Yellow\n      sgr(UNDERLINED, ITALIC) + line,           // fg Green, bg Yellow, Underlined, Italic\n      sgr(NO_UNDERLINED, NO_ITALIC) + line,     // fg Green, bg Yellow\n      sgr(FG_RESET, ITALIC) + line,             // bg Yellow, Italic\n      sgr(BG_RESET) + line                      // Italic\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with color256 and style separately', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_P256_RED) + line,    // fg Red 256,\n      sgr(UNDERLINED) + line,     // fg Red 256, Underlined\n      sgr(FG_P256_GREEN) + line,  // fg Green 256, Underlined\n      sgr(INVERSE) + line,        // fg Green 256, Underlined, Inverse\n      sgr(NO_INVERSE) + line,     // fg Green 256, Underlined\n      sgr(INVERSE) + line,        // fg Green 256, Underlined, Inverse\n      sgr(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse\n      sgr(FG_RESET) + line,       // bg Yellow 256, Underlined, Inverse\n      sgr(BG_RESET) + line,       // Underlined, Inverse\n      sgr(NORMAL) + line          // Back to normal\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with color256 and style together', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_P256_RED) + line,                    // fg Red 256\n      sgr(FG_P256_GREEN, BG_P256_YELLOW) + line,  // fg Green 256, bg Yellow 256\n      sgr(UNDERLINED, ITALIC) + line,             // fg Green 256, bg Yellow 256, Underlined, Italic\n      sgr(NO_UNDERLINED, NO_ITALIC) + line,       // fg Green 256, bg Yellow 256\n      sgr(FG_RESET, ITALIC) + line,               // bg Yellow 256, Italic\n      sgr(BG_RESET) + line,                       // Italic\n      sgr(NORMAL) + line,                         // Back to normal\n      sgr(FG_P256_RED) + line,                    // fg Red 256\n      sgr(FG_P256_GREEN, BG_P256_YELLOW) + line,  // fg Green 256, bg Yellow 256\n      sgr(UNDERLINED, ITALIC) + line,             // fg Green 256, bg Yellow 256, Underlined, Italic\n      sgr(NO_UNDERLINED, NO_ITALIC) + line,       // fg Green 256, bg Yellow 256\n      sgr(FG_RESET, ITALIC) + line,               // bg Yellow 256, Italic\n      sgr(BG_RESET) + line                        // Italic\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_RGB_RED) + line,     // fg Red RGB,\n      sgr(UNDERLINED) + line,     // fg Red RGB, Underlined\n      sgr(FG_RGB_GREEN) + line,   // fg Green RGB, Underlined\n      sgr(INVERSE) + line,        // fg Green RGB, Underlined, Inverse\n      sgr(NO_INVERSE) + line,     // fg Green RGB, Underlined\n      sgr(INVERSE) + line,        // fg Green RGB, Underlined, Inverse\n      sgr(BG_RGB_YELLOW) + line,  // fg Green RGB, bg Yellow RGB, Underlined, Inverse\n      sgr(FG_RESET) + line,       // bg Yellow RGB, Underlined, Inverse\n      sgr(BG_RESET) + line,       // Underlined, Inverse\n      sgr(NORMAL) + line          // Back to normal\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {\n    const cols = 10;\n    const line = '+'.repeat(cols);\n    const lines: string[] = [\n      sgr(FG_RGB_RED) + line,                   // fg Red RGB\n      sgr(FG_RGB_GREEN, BG_RGB_YELLOW) + line,  // fg Green RGB, bg Yellow RGB\n      sgr(UNDERLINED, ITALIC) + line,           // fg Green RGB, bg Yellow RGB, Underlined, Italic\n      sgr(NO_UNDERLINED, NO_ITALIC) + line,     // fg Green RGB, bg Yellow RGB\n      sgr(FG_RESET, ITALIC) + line,             // bg Yellow RGB, Italic\n      sgr(BG_RESET) + line,                     // Italic\n      sgr(NORMAL) + line,                       // Back to normal\n      sgr(FG_RGB_RED) + line,                   // fg Red RGB\n      sgr(FG_RGB_GREEN, BG_RGB_YELLOW) + line,  // fg Green RGB, bg Yellow RGB\n      sgr(UNDERLINED, ITALIC) + line,           // fg Green RGB, bg Yellow RGB, Underlined, Italic\n      sgr(NO_UNDERLINED, NO_ITALIC) + line,     // fg Green RGB, bg Yellow RGB\n      sgr(FG_RESET, ITALIC) + line,             // bg Yellow RGB, Italic\n      sgr(BG_RESET) + line                      // Italic\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize tabs correctly', async () => {\n    const lines = [\n      'a\\tb',\n      'aa\\tc',\n      'aaa\\td'\n    ];\n    const expected = [\n      'a\\x1b[7Cb',\n      'aa\\x1b[6Cc',\n      'aaa\\x1b[5Cd'\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), expected.join('\\r\\n'));\n  });\n\n  test('serialize CJK correctly', async () => {\n    const lines = [\n      '中文中文',\n      '12中文',\n      '中文12',\n      // This line is going to be wrapped at last character\n      // because it has line length of 11 (1+2*5).\n      // We concat it back without the null cell currently.\n      // But this may be incorrect.\n      // see also #3097\n      '1中文中文中'\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\\r\\n'));\n  });\n\n  test('serialize CJK Mixed with tab correctly', async () => {\n    const lines = [\n      '中文\\t12' // CJK mixed with tab\n    ];\n    const expected = [\n      '中文\\x1b[4C12'\n    ];\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), expected.join('\\r\\n'));\n  });\n\n  test('serialize with alt screen correctly', async () => {\n    const SMCUP = '\\u001b[?1049h';\n    const CUP = '\\u001b[H';\n\n    const lines = [\n      `1${SMCUP}${CUP}2`\n    ];\n    const expected = [\n      `1${SMCUP}${CUP}2`\n    ];\n\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'alternate');\n    strictEqual(JSON.stringify(await ctx.page.evaluate(`window.serialize.serialize();`)), JSON.stringify(expected.join('\\r\\n')));\n  });\n\n  test('serialize without alt screen correctly', async () => {\n    const SMCUP = '\\u001b[?1049h';\n    const RMCUP = '\\u001b[?1049l';\n\n    const lines = [\n      `1${SMCUP}2${RMCUP}`\n    ];\n    const expected = [\n      `1`\n    ];\n\n    await ctx.proxy.write(lines.join('\\r\\n'));\n    strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'normal');\n    strictEqual(JSON.stringify(await ctx.page.evaluate(`window.serialize.serialize();`)), JSON.stringify(expected.join('\\r\\n')));\n  });\n\n  test('serialize with background', async () => {\n    const CLEAR_RIGHT = (l: number): string => `\\u001b[${l}X`;\n\n    const lines = [\n      `1\\u001b[44m${CLEAR_RIGHT(5)}`,\n      `2${CLEAR_RIGHT(9)}`\n    ];\n\n    await testNormalScreenEqual(ctx.page, lines.join('\\r\\n'));\n  });\n\n  test('cause the BCE on scroll', async () => {\n    const CLEAR_RIGHT = (l: number): string => `\\u001b[${l}X`;\n\n    const padLines = newArray<string>(\n      (index: number) => digitsString(10, index),\n      10\n    );\n\n    const lines = [\n      ...padLines,\n      `\\u001b[44m${CLEAR_RIGHT(5)}1111111111111111`\n    ];\n\n    await testNormalScreenEqual(ctx.page, lines.join('\\r\\n'));\n  });\n\n  test('handle invalid wrap before scroll', async () => {\n    const CLEAR_RIGHT = (l: number): string => `\\u001b[${l}X`;\n    const MOVE_UP = (l: number): string => `\\u001b[${l}A`;\n    const MOVE_DOWN = (l: number): string => `\\u001b[${l}B`;\n    const MOVE_LEFT = (l: number): string => `\\u001b[${l}D`;\n\n    // A line wrap happened after current line.\n    // But there is no content.\n    // so wrap shouldn't even be able to happen.\n    const segments = [\n      `123456789012345`,\n      MOVE_UP(1),\n      CLEAR_RIGHT(5),\n      MOVE_DOWN(1),\n      MOVE_LEFT(5),\n      CLEAR_RIGHT(5),\n      MOVE_UP(1),\n      '1'\n    ];\n\n    await testNormalScreenEqual(ctx.page, segments.join(''));\n  });\n\n  test('handle invalid wrap after scroll', async () => {\n    const CLEAR_RIGHT = (l: number): string => `\\u001b[${l}X`;\n    const MOVE_UP = (l: number): string => `\\u001b[${l}A`;\n    const MOVE_DOWN = (l: number): string => `\\u001b[${l}B`;\n    const MOVE_LEFT = (l: number): string => `\\u001b[${l}D`;\n\n    const padLines = newArray<string>(\n      (index: number) => digitsString(10, index),\n      10\n    );\n\n    // A line wrap happened after current line.\n    // But there is no content.\n    // so wrap shouldn't even be able to happen.\n    const lines = [\n      padLines.join('\\r\\n'),\n      '\\r\\n',\n      `123456789012345`,\n      MOVE_UP(1),\n      CLEAR_RIGHT(5),\n      MOVE_DOWN(1),\n      MOVE_LEFT(5),\n      CLEAR_RIGHT(5),\n      MOVE_UP(1),\n      '1'\n    ];\n\n    await testNormalScreenEqual(ctx.page, lines.join(''));\n  });\n\n  test.describe('handle modes', () => {\n    test('applicationCursorKeysMode', async () => {\n      await testSerializeEquals('test\\u001b[?1h', 'test\\u001b[?1h');\n      await testSerializeEquals('\\u001b[?1l', 'test');\n    });\n    test('applicationKeypadMode', async () => {\n      await testSerializeEquals('test\\u001b[?66h', 'test\\u001b[?66h');\n      await testSerializeEquals('\\u001b[?66l', 'test');\n    });\n    test('bracketedPasteMode', async () => {\n      await testSerializeEquals('test\\u001b[?2004h', 'test\\u001b[?2004h');\n      await testSerializeEquals('\\u001b[?2004l', 'test');\n    });\n    test('insertMode', async () => {\n      await testSerializeEquals('test\\u001b[4h', 'test\\u001b[4h');\n      await testSerializeEquals('\\u001b[4l', 'test');\n    });\n    test('mouseTrackingMode', async () => {\n      await testSerializeEquals('test\\u001b[?9h', 'test\\u001b[?9h');\n      await testSerializeEquals('\\u001b[?9l', 'test');\n      await testSerializeEquals('\\u001b[?1000h', 'test\\u001b[?1000h');\n      await testSerializeEquals('\\u001b[?1000l', 'test');\n      await testSerializeEquals('\\u001b[?1002h', 'test\\u001b[?1002h');\n      await testSerializeEquals('\\u001b[?1002l', 'test');\n      await testSerializeEquals('\\u001b[?1003h', 'test\\u001b[?1003h');\n      await testSerializeEquals('\\u001b[?1003l', 'test');\n    });\n    test('originMode', async () => {\n      // origin mode moves cursor to (0,0)\n      await testSerializeEquals('test\\u001b[?6h', 'test\\u001b[4D\\u001b[?6h');\n      await testSerializeEquals('\\u001b[?6l', 'test\\u001b[4D');\n    });\n    test('reverseWraparoundMode', async () => {\n      await testSerializeEquals('test\\u001b[?45h', 'test\\u001b[?45h');\n      await testSerializeEquals('\\u001b[?45l', 'test');\n    });\n    test('sendFocusMode', async () => {\n      await testSerializeEquals('test\\u001b[?1004h', 'test\\u001b[?1004h');\n      await testSerializeEquals('\\u001b[?1004l', 'test');\n    });\n    test('wraparoundMode', async () => {\n      await testSerializeEquals('test\\u001b[?7l', 'test\\u001b[?7l');\n      await testSerializeEquals('\\u001b[?7h', 'test');\n    });\n  });\n});\n\nfunction newArray<T>(initial: T | ((index: number) => T), count: number): T[] {\n  const array: T[] = new Array<T>(count);\n  for (let i = 0; i < array.length; i++) {\n    if (typeof initial === 'function') {\n      array[i] = (initial as (index: number) => T)(i);\n    } else {\n      array[i] = initial as T;\n    }\n  }\n  return array;\n}\n\nfunction digitsString(length: number, from: number = 0, sgr: string = ''): string {\n  let s = sgr;\n  for (let i = 0; i < length; i++) {\n    s += `${(from++) % 10}`;\n  }\n  return s;\n}\n\nfunction sgr(...seq: string[]): string {\n  return `\\x1b[${seq.join(';')}m`;\n}\n\nconst NORMAL = '0';\n\nconst FG_P16_RED = '31';\nconst FG_P16_GREEN = '32';\nconst FG_P16_YELLOW = '33';\nconst FG_P256_RED = '38;5;196';\nconst FG_P256_GREEN = '38;5;46';\nconst FG_P256_YELLOW = '38;5;226';\nconst FG_RGB_RED = '38;2;255;0;0';\nconst FG_RGB_GREEN = '38;2;0;255;0';\nconst FG_RGB_YELLOW = '38;2;255;255;0';\nconst FG_RESET = '39';\n\nconst BG_P16_RED = '41';\nconst BG_P16_GREEN = '42';\nconst BG_P16_YELLOW = '43';\nconst BG_P256_RED = '48;5;196';\nconst BG_P256_GREEN = '48;5;46';\nconst BG_P256_YELLOW = '48;5;226';\nconst BG_RGB_RED = '48;2;255;0;0';\nconst BG_RGB_GREEN = '48;2;0;255;0';\nconst BG_RGB_YELLOW = '48;2;255;255;0';\nconst BG_RESET = '49';\n\nconst BOLD = '1';\nconst DIM = '2';\nconst ITALIC = '3';\nconst UNDERLINED = '4';\nconst UNDERLINE_DOUBLE = '4:2';\nconst UNDERLINE_COLOR_RED = '58;5;196';\nconst UNDERLINE_COLOR_GREEN = '58;5;46';\nconst BLINK = '5';\nconst INVERSE = '7';\nconst INVISIBLE = '8';\nconst STRIKETHROUGH = '9';\nconst OVERLINED = '53';\n\nconst NO_BOLD = '22';\nconst NO_DIM = '22';\nconst NO_ITALIC = '23';\nconst NO_UNDERLINED = '24';\nconst NO_BLINK = '25';\nconst NO_INVERSE = '27';\nconst NO_INVISIBLE = '28';\nconst NO_STRIKETHROUGH = '29';\nconst NO_OVERLINED = '55';\n"
  },
  {
    "path": "addons/addon-serialize/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-serialize/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-serialize/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" },\n    { \"path\": \"./benchmark\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-serialize/typings/addon-serialize.d.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon, IMarker, IBufferRange } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-serialize' {\n  /**\n   * An xterm.js addon that enables serialization of terminal contents.\n   */\n  export class SerializeAddon implements ITerminalAddon {\n\n    constructor();\n\n    /**\n     * Activates the addon.\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Serializes terminal rows into a string that can be written back to the terminal to restore\n     * the state. The cursor will also be positioned to the correct cell. When restoring a terminal\n     * it is best to do before `Terminal.open` is called to avoid wasting CPU cycles rendering\n     * incomplete frames.\n     *\n     * It's recommended that you write the serialized data into a terminal of the same size in which\n     * it originated from and then resize it after if needed.\n     *\n     * @param options Custom options to allow control over what gets serialized.\n     */\n    public serialize(options?: ISerializeOptions): string;\n\n    /**\n     * Serializes terminal content as HTML, which can be written to the clipboard using the\n     * `text/html` mimetype. For applications that support it, the pasted text should then retain\n     * its colors/styles.\n     *\n     * @param options Custom options to allow control over what gets serialized.\n     */\n    public serializeAsHTML(options?: Partial<IHTMLSerializeOptions>): string;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n  }\n\n  export interface ISerializeOptions {\n    /**\n     * The row range to serialize. The an explicit range is specified, the cursor will get its final\n     * repositioning.\n     */\n    range?: ISerializeRange;\n\n    /**\n     * The number of rows in the scrollback buffer to serialize, starting from the bottom of the\n     * scrollback buffer. When not specified, all available rows in the scrollback buffer will be\n     * serialized. This will be ignored if {@link range} is specified.\n     */\n    scrollback?: number;\n\n    /**\n     * Whether to exclude the terminal modes from the serialization. False by default.\n     */\n    excludeModes?: boolean;\n\n    /**\n     * Whether to exclude the alt buffer from the serialization. False by default.\n     */\n    excludeAltBuffer?: boolean;\n  }\n\n  export interface IHTMLSerializeOptions {\n    /**\n     * The number of rows in the scrollback buffer to serialize, starting from the bottom of the\n     * scrollback buffer. When not specified, all available rows in the scrollback buffer will be\n     * serialized. This setting is ignored if {@link IHTMLSerializeOptions.onlySelection} is true.\n     */\n    scrollback: number;\n\n    /**\n     * Whether to only serialize the selection. If false, the whole active buffer is serialized in HTML.\n     * False by default.\n     */\n    onlySelection: boolean;\n\n    /**\n     * Whether to include the global background of the terminal. False by default.\n     */\n    includeGlobalBackground: boolean;\n\n     /**\n     * The range to serialize. This is prioritized over {@link onlySelection}.\n     */\n    range?: ISerializeBufferRange;\n  }\n\n  export interface ISerializeBufferRange {\n    startLine: number;\n    endLine: number;\n    startCol: number;\n  }\n\n  export interface ISerializeRange {\n    /**\n     * The line to start serializing (inclusive).\n     */\n    start: IMarker | number;\n    /**\n     * The line to end serializing (inclusive).\n     */\n    end: IMarker | number;\n  }\n}\n"
  },
  {
    "path": "addons/addon-serialize/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'SerializeAddon';\nconst mainFile = 'addon-serialize.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      browser: path.resolve('../../out/browser')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/.gitignore",
    "content": "lib\nnode_modules\nout-benchmark\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/LICENSE",
    "content": "Copyright (c) 2023, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/README.md",
    "content": "## @xterm/addon-unicode-graphemes\n\n⚠️ **This addon is currently experimental and may introduce unexpected and non-standard behavior**\n\nAn addon providing enhanced Unicode support (include grapheme clustering) for xterm.js.\n\nThe file `src/UnicodeProperties.ts` is generated and depends on the Unicode version. See [the unicode-properties project](https://github.com/PerBothner/unicode-properties) for credits and re-generation instructions.\n\n### Install\n\nThis addon is not yet published to npm\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';\n\nconst terminal = new Terminal();\nconst unicodeGraphemesAddon = new UnicodeGraphemesAddon();\nterminal.loadAddon(unicodeGraphemesAddon);\n```\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/benchmark/UnicodeGraphemeAddon.benchmark.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';\n\nimport { spawn } from 'node-pty';\nimport { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';\nimport { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';\nimport { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';\n\n\nfunction fakedAddonLoad(terminal: any): void {\n  // resembles what UnicodeGraphemesAddon.activate does under the hood\n  terminal.unicodeService.register(new UnicodeGraphemeProvider());\n  terminal.unicodeService.activeVersion = '15-graphemes';\n}\n\n\nperfContext('Terminal: ls -lR /usr/lib', () => {\n  let content = '';\n  let contentUtf8: Uint8Array;\n\n  before(async () => {\n    // grab output from \"ls -lR /usr\"\n    const p = spawn('ls', ['--color=auto', '-lR', '/usr/lib'], {\n      name: 'xterm-256color',\n      cols: 80,\n      rows: 25,\n      cwd: process.env.HOME,\n      env: process.env,\n      encoding: (null as unknown as string) // needs to be fixed in node-pty\n    });\n    const chunks: Buffer[] = [];\n    let length = 0;\n    p.onData(data => {\n      chunks.push(data as unknown as Buffer);\n      length += data.length;\n    });\n    await new Promise<void>(resolve => p.onExit(() => resolve()));\n    contentUtf8 = Buffer.concat(chunks, length);\n    // translate to content string\n    const buffer = new Uint32Array(contentUtf8.length);\n    const decoder = new Utf8ToUtf32();\n    const codepoints = decoder.decode(contentUtf8, buffer);\n    for (let i = 0; i < codepoints; ++i) {\n      content += stringFromCodePoint(buffer[i]);\n      // peek into content to force flat repr in v8\n      if (!(i % 10000000)) {\n        content[i];\n      }\n    }\n  });\n\n  perfContext('write/string/async', () => {\n    let terminal: CoreBrowserTerminal;\n    before(() => {\n      terminal = new CoreBrowserTerminal({ cols: 80, rows: 25, scrollback: 1000 });\n      fakedAddonLoad(terminal);\n    });\n    new ThroughputRuntimeCase('', async () => {\n      await new Promise<void>(res => terminal.write(content, res));\n      return { payloadSize: contentUtf8.length };\n    }, { fork: false }).showAverageThroughput();\n  });\n\n  perfContext('write/Utf8/async', () => {\n    let terminal: CoreBrowserTerminal;\n    before(() => {\n      terminal = new CoreBrowserTerminal({ cols: 80, rows: 25, scrollback: 1000 });\n    });\n    new ThroughputRuntimeCase('', async () => {\n      await new Promise<void>(res => terminal.write(content, res));\n      return { payloadSize: contentUtf8.length };\n    }, { fork: false }).showAverageThroughput();\n  });\n});\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/benchmark/benchmark.json",
    "content": "{\n  \"APP_PATH\": \".benchmark\",\n  \"evalConfig\": {\n    \"tolerance\": {\n      \"*\": [0.75, 1.5],\n      \"*.dev\": [0.01, 1.5],\n      \"*.cv\": [0.01, 1.5],\n      \"EscapeSequenceParser.benchmark.js.*.averageThroughput.mean\": [0.9, 5]\n    },\n    \"skip\": [\n      \"*.median\",\n      \"*.runs\",\n      \"*.dev\",\n      \"*.cv\",\n      \"EscapeSequenceParser.benchmark.js.*.averageRuntime\",\n      \"Terminal.benchmark.js.*.averageRuntime\"\n    ]\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/benchmark/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"es6\"],\n    \"rootDir\": \"..\",\n    \"outDir\": \"../out-benchmark\",\n    \"types\": [\"../../../node_modules/@types/node\"],\n    \"moduleResolution\": \"node\",\n    \"strict\": false,\n    \"target\": \"es2015\",\n    \"module\": \"commonjs\",\n    \"paths\": {\n      \"common/*\": [\"../../../src/common/*\"],\n      \"browser/*\": [\"../../../src/browser/*\"],\n      \"UnicodeGraphemeProvider\": [\"../src/UnicodeGraphemeProvider\"],\n      \"@xterm/addon-unicode-graphemes\": [\n        \"../typings/addon-unicode-graphemes.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\"../**/*\", \"../../../typings/xterm.d.ts\"],\n  \"exclude\": [\"../../../**/*test.ts\", \"../../**/*api.ts\"],\n  \"references\": [\n    { \"path\": \"../../../src/common\" },\n    { \"path\": \"../../../src/browser\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-unicode-graphemes\",\n  \"version\": \"0.4.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-unicode-graphemes.js\",\n  \"module\": \"lib/addon-unicode-graphemes.mjs\",\n  \"types\": \"typings/addon-unicode-graphemes.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode-graphemes\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\",\n    \"benchmark\": \"NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json out-benchmark/benchmark/*benchmark.js\",\n    \"benchmark-baseline\": \"NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js\",\n    \"benchmark-eval\": \"NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/src/UnicodeGraphemeProvider.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IUnicodeVersionProvider } from '@xterm/xterm';\nimport { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';\nimport { UnicodeService } from 'common/services/UnicodeService';\nimport * as UC from './third-party/UnicodeProperties';\n\nexport class UnicodeGraphemeProvider implements IUnicodeVersionProvider {\n  public readonly version;\n  public ambiguousCharsAreWide: boolean = false;\n  public readonly handleGraphemes: boolean;\n\n  constructor(handleGraphemes: boolean = true) {\n    this.version = handleGraphemes ? '15-graphemes' : '15';\n    this.handleGraphemes = handleGraphemes;\n  }\n\n  private static readonly _plainNarrowProperties: UnicodeCharProperties\n    = UnicodeService.createPropertyValue(UC.GRAPHEME_BREAK_Other, 1, false);\n\n  public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {\n    // Optimize the simple ASCII case, under the condition that\n    // UnicodeService.extractCharKind(preceding) === GRAPHEME_BREAK_Other\n    // (which also covers the case that preceding === 0).\n    if ((codepoint >= 32 && codepoint < 127) && (preceding >> 3) === 0) {\n      return UnicodeGraphemeProvider._plainNarrowProperties;\n    }\n\n    let charInfo = UC.getInfo(codepoint);\n    let w = UC.infoToWidthInfo(charInfo);\n    let shouldJoin = false;\n    if (w >= 2) {\n      // Treat emoji_presentation_selector as WIDE.\n      w = w === 3 || this.ambiguousCharsAreWide || codepoint === 0xfe0f ? 2 : 1;\n    } else {\n      w = 1;\n    }\n    if (preceding !== 0) {\n      const oldWidth = UnicodeService.extractWidth(preceding);\n      if (this.handleGraphemes) {\n        charInfo = UC.shouldJoin(UnicodeService.extractCharKind(preceding), charInfo);\n      } else {\n        charInfo = w === 0 ? 1 : 0;\n      }\n      shouldJoin = charInfo > 0;\n      if (shouldJoin) {\n        if (oldWidth > w) {\n          w = oldWidth;\n        } else if (charInfo === 32) { // UC.GRAPHEME_BREAK_SAW_Regional_Pair)\n          w = 2;\n        }\n      }\n    }\n    return UnicodeService.createPropertyValue(charInfo, w, shouldJoin);\n  }\n\n  public wcwidth(codepoint: number): UnicodeCharWidth {\n    const charInfo = UC.getInfo(codepoint);\n    const w = UC.infoToWidthInfo(charInfo);\n    const kind = (charInfo & UC.GRAPHEME_BREAK_MASK) >> UC.GRAPHEME_BREAK_SHIFT;\n    if (kind === UC.GRAPHEME_BREAK_Extend || kind === UC.GRAPHEME_BREAK_Prepend) {\n      return 0;\n    }\n    if (w >= 2 && (w === 3 || this.ambiguousCharsAreWide)) {\n      return 2;\n    }\n    return 1;\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * UnicodeVersionProvider for V15 with grapeme cluster handleing.\n */\n\nimport type { Terminal, ITerminalAddon, IUnicodeHandling } from '@xterm/xterm';\nimport type { UnicodeGraphemesAddon as IUnicodeGraphemesApi } from '@xterm/addon-unicode-graphemes';\nimport { UnicodeGraphemeProvider } from './UnicodeGraphemeProvider';\n\nexport class UnicodeGraphemesAddon implements ITerminalAddon, IUnicodeGraphemesApi {\n  private _provider15Graphemes?: UnicodeGraphemeProvider;\n  private _provider15?: UnicodeGraphemeProvider;\n  private _unicode?: IUnicodeHandling;\n  private _oldVersion: string = '';\n\n  public activate(terminal: Terminal): void {\n    this._provider15 ??= new UnicodeGraphemeProvider(false);\n    this._provider15Graphemes ??= new UnicodeGraphemeProvider(true);\n    const unicode = terminal.unicode;\n    this._unicode = unicode;\n    unicode.register(this._provider15);\n    unicode.register(this._provider15Graphemes);\n    this._oldVersion = unicode.activeVersion;\n    unicode.activeVersion = '15-graphemes';\n  }\n\n  public dispose(): void {\n    if (this._unicode) {\n      this._unicode.activeVersion = this._oldVersion;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts",
    "content": "import UnicodeTrie from './unicode-trie';\nconst trieRaw = \"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\";\n\ndeclare const Buffer: { from(s: string, encoding: string): Uint8Array } | undefined;\nfunction _dec(s: string): Uint8Array {\n    if (typeof Buffer !== 'undefined') return Buffer.from(s, 'base64');\n    const bs = atob(s);\n    const r = new Uint8Array(bs.length);\n    for (let i = 0; i < r.length; ++i) r[i] = bs.charCodeAt(i);\n    return r;\n}\n\nconst trieData = new UnicodeTrie(_dec(trieRaw));\nexport const GRAPHEME_BREAK_MASK = 0xF;\nexport const GRAPHEME_BREAK_SHIFT = 0;\nexport const CHARWIDTH_MASK = 0x30;\nexport const CHARWIDTH_SHIFT = 4;\n\n// Values for the GRAPHEME_BREAK property\nexport const GRAPHEME_BREAK_Other = 0; // includes CR, LF, Control\nexport const GRAPHEME_BREAK_Prepend = 1;\nexport const GRAPHEME_BREAK_Extend = 2;\nexport const GRAPHEME_BREAK_Regional_Indicator = 3;\nexport const GRAPHEME_BREAK_SpacingMark = 4;\nexport const GRAPHEME_BREAK_Hangul_L = 5;\nexport const GRAPHEME_BREAK_Hangul_V = 6;\nexport const GRAPHEME_BREAK_Hangul_T = 7;\nexport const GRAPHEME_BREAK_Hangul_LV = 8;\nexport const GRAPHEME_BREAK_Hangul_LVT = 9;\nexport const GRAPHEME_BREAK_ZWJ = 10;\nexport const GRAPHEME_BREAK_ExtPic = 11;\n\n// Only used as return value from shouldJoin/shouldJoinBackwards.\n// (Must be positive; distinct from other values;\n// and become GRAPHEME_BREAK_Other when masked with GRAPHEME_BREAK_MASK.)\nconst GRAPHEME_BREAK_SAW_Regional_Pair = 32;\n\nexport const CHARWIDTH_NORMAL = 0;\nexport const CHARWIDTH_FORCE_1COLUMN = 1;\nexport const CHARWIDTH_EA_AMBIGUOUS = 2;\nexport const CHARWIDTH_WIDE = 3;\n\n// In the following 'info' is an encoded value from trie.get(codePoint)\n\n// In the following 'info' is an encoded value from trie.get(codePoint)\n\nexport function infoToWidthInfo(info: number): number {\n    return (info & CHARWIDTH_MASK) >> CHARWIDTH_SHIFT;\n}\n\nexport function infoToWidth(info: number, ambiguousIsWide = false): 0 | 1 |2 {\n    const v = infoToWidthInfo(info);\n    return v < CHARWIDTH_EA_AMBIGUOUS ? 1\n        : v >= CHARWIDTH_WIDE || ambiguousIsWide ? 2 : 1;\n}\n\nexport function strWidth(str: string, preferWide: boolean): number {\n    let width = 0;\n    for (let i = 0; i < str.length;) {\n        const codePoint = str.codePointAt(i) as number;\n        width += infoToWidth(getInfo(codePoint), preferWide);\n        i += (codePoint <= 0xffff) ? 1 : 2;\n    }\n    return width;\n}\n\nexport function columnToIndexInContext(str: string, startIndex: number, column: number, preferWide: boolean): number {\n    let rv = 0;\n    for (let i = startIndex; ;) {\n\tif (i >= str.length)\n\t    return i;\n\tconst codePoint = str.codePointAt(i) as number;\n\tconst w = infoToWidth(getInfo(codePoint), preferWide);\n\trv += w;\n\tif (rv > column)\n\t    return i;\n\ti += (codePoint <= 0xffff) ? 1 : 2;\n    }\n}\n\n\n// Test if should break between beforeState and afterCode.\n// Return <= 0 if should break; > 0 if should join.\n// 'beforeState' is  the return value from the previous possible break;\n// the value 0 is start of string.\n// 'afterCode' is the GRAPHEME_BREAK_Xxx value for the following codepoint.\nexport function shouldJoin(beforeState: number, afterInfo: number): number {\n    let beforeCode = (beforeState & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT;\n    let afterCode = (afterInfo & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT;\n    if (_shouldJoin(beforeCode, afterCode)) {\n        if (afterCode === GRAPHEME_BREAK_Regional_Indicator)\n            return GRAPHEME_BREAK_SAW_Regional_Pair;\n        else\n            return afterCode + 16;\n    } else\n        return afterCode - 16;\n}\n\nexport function shouldJoinBackwards(beforeInfo: number, afterState: number): number {\n    let afterCode = (afterState & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT;\n    let beforeCode = (beforeInfo & GRAPHEME_BREAK_MASK) >> GRAPHEME_BREAK_SHIFT;\n    if (_shouldJoin(beforeCode, afterCode)) {\n        if (beforeCode === GRAPHEME_BREAK_Regional_Indicator)\n            return GRAPHEME_BREAK_SAW_Regional_Pair;\n        else\n            return beforeCode + 16;\n    } else\n        return beforeCode - 16;\n}\n\n/** Doesn't handle an odd number of RI characters. */\nfunction _shouldJoin(beforeCode: number, afterCode: number): boolean {\n    if (beforeCode >= GRAPHEME_BREAK_Hangul_L\n        && beforeCode <= GRAPHEME_BREAK_Hangul_LVT) {\n        if (beforeCode == GRAPHEME_BREAK_Hangul_L // GB6\n            && (afterCode == GRAPHEME_BREAK_Hangul_L\n                || afterCode == GRAPHEME_BREAK_Hangul_V\n                || afterCode == GRAPHEME_BREAK_Hangul_LV\n                || afterCode == GRAPHEME_BREAK_Hangul_LVT))\n            return true;\n        if ((beforeCode == GRAPHEME_BREAK_Hangul_LV // GB7\n             || beforeCode == GRAPHEME_BREAK_Hangul_V)\n            && (afterCode == GRAPHEME_BREAK_Hangul_V\n                || afterCode == GRAPHEME_BREAK_Hangul_T))\n            return true;\n        if ((beforeCode == GRAPHEME_BREAK_Hangul_LVT // GB8\n             || beforeCode == GRAPHEME_BREAK_Hangul_T)\n            && afterCode == GRAPHEME_BREAK_Hangul_T)\n            return true;\n    }\n    if (afterCode == GRAPHEME_BREAK_Extend // GB9\n        || afterCode == GRAPHEME_BREAK_ZWJ\n        || beforeCode == GRAPHEME_BREAK_Prepend // GB9a\n        || afterCode == GRAPHEME_BREAK_SpacingMark) // GB9b\n        return true;\n    if (beforeCode == GRAPHEME_BREAK_ZWJ // GB11\n        && afterCode == GRAPHEME_BREAK_ExtPic)\n        return true;\n    if (afterCode == GRAPHEME_BREAK_Regional_Indicator // GB12, GB13\n        && beforeCode == GRAPHEME_BREAK_Regional_Indicator)\n        return true;\n    return false;\n}\n\nexport function getInfo(codePoint: number): number {\n    return trieData.get(codePoint);\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/src/third-party/tiny-inflate.ts",
    "content": "var TINF_OK = 0;\nvar TINF_DATA_ERROR = -3;\n\nclass Tree {\n  table = new Uint16Array(16);   /* table of code length counts */\n  trans = new Uint16Array(288);  /* code -> symbol translation table */\n};\n\nclass Data {\n  tag: number = 0;\n  bitcount: number = 0;\n  destLen: number = 0;\n  ltree: Tree;\n  dtree: Tree;\n  source: Uint8Array;\n  dest: Uint8Array;\n  sourceIndex: number = 0;\n\n  constructor(source: Uint8Array, dest: Uint8Array) {\n    this.source = source;\n    this.dest = dest;\n    this.ltree = new Tree();  /* dynamic length/symbol tree */\n    this.dtree = new Tree();  /* dynamic distance tree */\n  }\n}\n\n/* --------------------------------------------------- *\n * -- uninitialized global data (static structures) -- *\n * --------------------------------------------------- */\n\nvar sltree = new Tree();\nvar sdtree = new Tree();\n\n/* extra bits and base tables for length codes */\nvar length_bits = new Uint8Array(30);\nvar length_base = new Uint16Array(30);\n\n/* extra bits and base tables for distance codes */\nvar dist_bits = new Uint8Array(30);\nvar dist_base = new Uint16Array(30);\n\n/* special ordering of code length codes */\nvar clcidx = new Uint8Array([\n  16, 17, 18, 0, 8, 7, 9, 6,\n  10, 5, 11, 4, 12, 3, 13, 2,\n  14, 1, 15\n]);\n\n/* used by tinf_decode_trees, avoids allocations every call */\nconst code_tree = new Tree();\nconst lengths = new Uint8Array(288 + 32);\n\n/* ----------------------- *\n * -- utility functions -- *\n * ----------------------- */\n\n/* build extra bits and base tables */\nfunction tinf_build_bits_base(bits: Uint8Array, base: Uint16Array, delta: number, first: number): void {\n  var i, sum;\n\n  /* build bits table */\n  for (i = 0; i < delta; ++i) bits[i] = 0;\n  for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0;\n\n  /* build base table */\n  for (sum = first, i = 0; i < 30; ++i) {\n    base[i] = sum;\n    sum += 1 << bits[i];\n  }\n}\n\n/* build the fixed huffman trees */\nfunction tinf_build_fixed_trees(lt: Tree, dt: Tree): void {\n  var i;\n\n  /* build fixed length tree */\n  for (i = 0; i < 7; ++i) lt.table[i] = 0;\n\n  lt.table[7] = 24;\n  lt.table[8] = 152;\n  lt.table[9] = 112;\n\n  for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;\n  for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;\n  for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;\n  for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;\n\n  /* build fixed distance tree */\n  for (i = 0; i < 5; ++i) dt.table[i] = 0;\n\n  dt.table[5] = 32;\n\n  for (i = 0; i < 32; ++i) dt.trans[i] = i;\n}\n\n/* given an array of code lengths, build a tree */\nvar offs = new Uint16Array(16);\n\nfunction tinf_build_tree(t: Tree, lengths: Uint8Array, off: number, num: number): void {\n  var i, sum;\n\n  /* clear code length count table */\n  for (i = 0; i < 16; ++i) t.table[i] = 0;\n\n  /* scan symbol lengths, and sum code length counts */\n  for (i = 0; i < num; ++i) t.table[lengths[off + i]]++;\n\n  t.table[0] = 0;\n\n  /* compute offset table for distribution sort */\n  for (sum = 0, i = 0; i < 16; ++i) {\n    offs[i] = sum;\n    sum += t.table[i];\n  }\n\n  /* create code->symbol translation table (symbols sorted by code) */\n  for (i = 0; i < num; ++i) {\n    if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i;\n  }\n}\n\n/* ---------------------- *\n * -- decode functions -- *\n * ---------------------- */\n\n/* get one bit from source stream */\nfunction tinf_getbit(d: Data): number {\n  /* check if tag is empty */\n  if (!d.bitcount--) {\n    /* load next tag */\n    d.tag = d.source[d.sourceIndex++];\n    d.bitcount = 7;\n  }\n\n  /* shift bit out of tag */\n  var bit = d.tag & 1;\n  d.tag >>>= 1;\n\n  return bit;\n}\n\n/* read a num bit value from a stream and add base */\nfunction tinf_read_bits(d: Data, num: number, base: number): number {\n  if (!num)\n    return base;\n\n  while (d.bitcount < 24) {\n    d.tag |= d.source[d.sourceIndex++] << d.bitcount;\n    d.bitcount += 8;\n  }\n\n  var val = d.tag & (0xffff >>> (16 - num));\n  d.tag >>>= num;\n  d.bitcount -= num;\n  return val + base;\n}\n\n/* given a data stream and a tree, decode a symbol */\nfunction tinf_decode_symbol(d: Data, t: Tree): number {\n  while (d.bitcount < 24) {\n    d.tag |= d.source[d.sourceIndex++] << d.bitcount;\n    d.bitcount += 8;\n  }\n  \n  var sum = 0, cur = 0, len = 0;\n  var tag = d.tag;\n\n  /* get more bits while code value is above sum */\n  do {\n    cur = 2 * cur + (tag & 1);\n    tag >>>= 1;\n    ++len;\n\n    sum += t.table[len];\n    cur -= t.table[len];\n  } while (cur >= 0);\n  \n  d.tag = tag;\n  d.bitcount -= len;\n\n  return t.trans[sum + cur];\n}\n\n/* given a data stream, decode dynamic trees from it */\nfunction tinf_decode_trees(d: Data, lt: Tree, dt: Tree): void {\n  var hlit, hdist, hclen;\n  var i, num, length;\n\n  /* get 5 bits HLIT (257-286) */\n  hlit = tinf_read_bits(d, 5, 257);\n\n  /* get 5 bits HDIST (1-32) */\n  hdist = tinf_read_bits(d, 5, 1);\n\n  /* get 4 bits HCLEN (4-19) */\n  hclen = tinf_read_bits(d, 4, 4);\n\n  for (i = 0; i < 19; ++i) lengths[i] = 0;\n\n  /* read code lengths for code length alphabet */\n  for (i = 0; i < hclen; ++i) {\n    /* get 3 bits code length (0-7) */\n    var clen = tinf_read_bits(d, 3, 0);\n    lengths[clcidx[i]] = clen;\n  }\n\n  /* build code length tree */\n  tinf_build_tree(code_tree, lengths, 0, 19);\n\n  /* decode code lengths for the dynamic trees */\n  for (num = 0; num < hlit + hdist;) {\n    var sym = tinf_decode_symbol(d, code_tree);\n\n    switch (sym) {\n      case 16:\n        /* copy previous code length 3-6 times (read 2 bits) */\n        var prev = lengths[num - 1];\n        for (length = tinf_read_bits(d, 2, 3); length; --length) {\n          lengths[num++] = prev;\n        }\n        break;\n      case 17:\n        /* repeat code length 0 for 3-10 times (read 3 bits) */\n        for (length = tinf_read_bits(d, 3, 3); length; --length) {\n          lengths[num++] = 0;\n        }\n        break;\n      case 18:\n        /* repeat code length 0 for 11-138 times (read 7 bits) */\n        for (length = tinf_read_bits(d, 7, 11); length; --length) {\n          lengths[num++] = 0;\n        }\n        break;\n      default:\n        /* values 0-15 represent the actual code lengths */\n        lengths[num++] = sym;\n        break;\n    }\n  }\n\n  /* build dynamic trees */\n  tinf_build_tree(lt, lengths, 0, hlit);\n  tinf_build_tree(dt, lengths, hlit, hdist);\n}\n\n/* ----------------------------- *\n * -- block inflate functions -- *\n * ----------------------------- */\n\n/* given a stream and two trees, inflate a block of data */\nfunction tinf_inflate_block_data(d: Data, lt: Tree, dt: Tree): number {\n  for (;;) {\n    var sym = tinf_decode_symbol(d, lt);\n\n    /* check for end of block */\n    if (sym === 256) {\n      return TINF_OK;\n    }\n\n    if (sym < 256) {\n      d.dest[d.destLen++] = sym;\n    } else {\n      var length, dist, offs;\n      var i;\n\n      sym -= 257;\n\n      /* possibly get more bits from length code */\n      length = tinf_read_bits(d, length_bits[sym], length_base[sym]);\n\n      dist = tinf_decode_symbol(d, dt);\n\n      /* possibly get more bits from distance code */\n      offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]);\n\n      /* copy match */\n      for (i = offs; i < offs + length; ++i) {\n        d.dest[d.destLen++] = d.dest[i];\n      }\n    }\n  }\n}\n\n/* inflate an uncompressed block of data */\nfunction tinf_inflate_uncompressed_block(d: Data) {\n  var length, invlength;\n  var i;\n  \n  /* unread from bitbuffer */\n  while (d.bitcount > 8) {\n    d.sourceIndex--;\n    d.bitcount -= 8;\n  }\n\n  /* get length */\n  length = d.source[d.sourceIndex + 1];\n  length = 256 * length + d.source[d.sourceIndex];\n\n  /* get one's complement of length */\n  invlength = d.source[d.sourceIndex + 3];\n  invlength = 256 * invlength + d.source[d.sourceIndex + 2];\n\n  /* check length */\n  if (length !== (~invlength & 0x0000ffff))\n    return TINF_DATA_ERROR;\n\n  d.sourceIndex += 4;\n\n  /* copy block */\n  for (i = length; i; --i)\n    d.dest[d.destLen++] = d.source[d.sourceIndex++];\n\n  /* make sure we start next block on a byte boundary */\n  d.bitcount = 0;\n\n  return TINF_OK;\n}\n\n/* inflate stream from source to dest */\nfunction tinf_uncompress(source: Uint8Array, dest: Uint8Array) {\n  var d = new Data(source, dest);\n  var bfinal, btype, res;\n\n  do {\n    /* read final block flag */\n    bfinal = tinf_getbit(d);\n\n    /* read block type (2 bits) */\n    btype = tinf_read_bits(d, 2, 0);\n\n    /* decompress block */\n    switch (btype) {\n      case 0:\n        /* decompress uncompressed block */\n        res = tinf_inflate_uncompressed_block(d);\n        break;\n      case 1:\n        /* decompress block with fixed huffman trees */\n        res = tinf_inflate_block_data(d, sltree, sdtree);\n        break;\n      case 2:\n        /* decompress block with dynamic huffman trees */\n        tinf_decode_trees(d, d.ltree, d.dtree);\n        res = tinf_inflate_block_data(d, d.ltree, d.dtree);\n        break;\n      default:\n        res = TINF_DATA_ERROR;\n    }\n\n    if (res !== TINF_OK)\n      throw new Error('Data error');\n\n  } while (!bfinal);\n\n  if (d.destLen < d.dest.length) {\n    if (typeof d.dest.slice === 'function')\n      return d.dest.slice(0, d.destLen);\n    else\n      return d.dest.subarray(0, d.destLen);\n  }\n  \n  return d.dest;\n}\n\n/* -------------------- *\n * -- initialization -- *\n * -------------------- */\n\n/* build fixed huffman trees */\ntinf_build_fixed_trees(sltree, sdtree);\n\n/* build extra bits and base tables */\ntinf_build_bits_base(length_bits, length_base, 4, 3);\ntinf_build_bits_base(dist_bits, dist_base, 2, 1);\n\n/* fix a special case */\nlength_bits[28] = 0;\nlength_base[28] = 258;\n\nexport default tinf_uncompress\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/src/third-party/unicode-trie.ts",
    "content": "import inflate from './tiny-inflate'\n\n// Shift size for getting the index-1 table offset.\nconst SHIFT_1 = 6 + 5;\n\n// Shift size for getting the index-2 table offset.\nconst SHIFT_2 = 5;\n\n// Difference between the two shift sizes,\n// for getting an index-1 offset from an index-2 offset. 6=11-5\nconst SHIFT_1_2 = SHIFT_1 - SHIFT_2;\n\n// Number of index-1 entries for the BMP. 32=0x20\n// This part of the index-1 table is omitted from the serialized form.\nconst OMITTED_BMP_INDEX_1_LENGTH = 0x10000 >> SHIFT_1;\n\n// Number of entries in an index-2 block. 64=0x40\nconst INDEX_2_BLOCK_LENGTH = 1 << SHIFT_1_2;\n\n// Mask for getting the lower bits for the in-index-2-block offset. */\nconst INDEX_2_MASK = INDEX_2_BLOCK_LENGTH - 1;\n\n// Shift size for shifting left the index array values.\n// Increases possible data size with 16-bit index values at the cost\n// of compactability.\n// This requires data blocks to be aligned by DATA_GRANULARITY.\nconst INDEX_SHIFT = 2;\n\n// Number of entries in a data block. 32=0x20\nconst DATA_BLOCK_LENGTH = 1 << SHIFT_2;\n\n// Mask for getting the lower bits for the in-data-block offset.\nconst DATA_MASK = DATA_BLOCK_LENGTH - 1;\n\n// The part of the index-2 table for U+D800..U+DBFF stores values for\n// lead surrogate code _units_ not code _points_.\n// Values for lead surrogate code _points_ are indexed with this portion of the table.\n// Length=32=0x20=0x400>>SHIFT_2. (There are 1024=0x400 lead surrogates.)\nconst LSCP_INDEX_2_OFFSET = 0x10000 >> SHIFT_2;\nconst LSCP_INDEX_2_LENGTH = 0x400 >> SHIFT_2;\n\n// Count the lengths of both BMP pieces. 2080=0x820\nconst INDEX_2_BMP_LENGTH = LSCP_INDEX_2_OFFSET + LSCP_INDEX_2_LENGTH;\n\n// The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820.\n// Length 32=0x20 for lead bytes C0..DF, regardless of SHIFT_2.\nconst UTF8_2B_INDEX_2_OFFSET = INDEX_2_BMP_LENGTH;\nconst UTF8_2B_INDEX_2_LENGTH = 0x800 >> 6;  // U+0800 is the first code point after 2-byte UTF-8\n\n// The index-1 table, only used for supplementary code points, at offset 2112=0x840.\n// Variable length, for code points up to highStart, where the last single-value range starts.\n// Maximum length 512=0x200=0x100000>>SHIFT_1.\n// (For 0x100000 supplementary code points U+10000..U+10ffff.)\n//\n// The part of the index-2 table for supplementary code points starts\n// after this index-1 table.\n//\n// Both the index-1 table and the following part of the index-2 table\n// are omitted completely if there is only BMP data.\nconst INDEX_1_OFFSET = UTF8_2B_INDEX_2_OFFSET + UTF8_2B_INDEX_2_LENGTH;\n\n// The alignment size of a data block. Also the granularity for compaction.\nconst DATA_GRANULARITY = 1 << INDEX_SHIFT;\n\nconst isBigEndian = (new Uint8Array(new Uint32Array([0x12345678]).buffer)[0] === 0x12);\n\nclass UnicodeTrie {\n    private data: Uint32Array;\n    private highStart: number;\n    private errorValue: number;\n  constructor(data: Uint8Array) {\n      // read binary format\n      \n        const view = new DataView(data.buffer);\n        this.highStart = view.getUint32(0, true);\n        this.errorValue = view.getUint32(4, true);\n        let uncompressedLength = view.getUint32(8, true);\n        data = data.subarray(12);\n\n      // double inflate the actual trie data\n      data = inflate(data, new Uint8Array(uncompressedLength));\n      data = inflate(data, new Uint8Array(uncompressedLength));\n\n      if (isBigEndian) {\n          // swap bytes from little-endian\n          const len = data.length;\n          for (let i = 0; i < len; i += 4) {\n              // Exchange data[i] and data[i + 3]:\n              let x = data[i]; data[i] = data[i+3]; data[i+3] = x;\n              // Exchange data[i + 1] and data[i + 2]:\n              let y = data[i+1]; data[i+1] = data[i+2]; data[i+2] = y;\n          }\n      }\n\n      this.data = new Uint32Array(data.buffer);\n\n  }\n\n    get(codePoint: number): number {\n    let index;\n    if ((codePoint < 0) || (codePoint > 0x10ffff)) {\n      return this.errorValue;\n    }\n\n    if ((codePoint < 0xd800) || ((codePoint > 0xdbff) && (codePoint <= 0xffff))) {\n      // Ordinary BMP code point, excluding leading surrogates.\n      // BMP uses a single level lookup.  BMP index starts at offset 0 in the index.\n      // data is stored in the index array itself.\n      index = (this.data[codePoint >> SHIFT_2] << INDEX_SHIFT) + (codePoint & DATA_MASK);\n      return this.data[index];\n    }\n\n    if (codePoint <= 0xffff) {\n      // Lead Surrogate Code Point.  A Separate index section is stored for\n      // lead surrogate code units and code points.\n      //   The main index has the code unit data.\n      //   For this function, we need the code point data.\n      index = (this.data[LSCP_INDEX_2_OFFSET + ((codePoint - 0xd800) >> SHIFT_2)] << INDEX_SHIFT) + (codePoint & DATA_MASK);\n      return this.data[index];\n    }\n\n    if (codePoint < this.highStart) {\n      // Supplemental code point, use two-level lookup.\n      index = this.data[(INDEX_1_OFFSET - OMITTED_BMP_INDEX_1_LENGTH) + (codePoint >> SHIFT_1)];\n      index = this.data[index + ((codePoint >> SHIFT_2) & INDEX_2_MASK)];\n      index = (index << INDEX_SHIFT) + (codePoint & DATA_MASK);\n      return this.data[index];\n    }\n\n    return this.data[this.data.length - DATA_GRANULARITY];\n  }\n}\n\nexport default UnicodeTrie\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2021\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"@xterm/addon-unicode-graphemes\": [\n        \"../typings/addon-unicode-graphemes.d.ts\"\n      ]\n    },\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/test/UnicodeGraphemesAddon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepStrictEqual, strictEqual } from 'assert';\nimport { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('UnicodeGraphemesAddon', () => {\n\n  test.beforeEach(async () => {\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.unicode?.dispose();\n      window.unicode = new UnicodeGraphemesAddon();\n      window.term.loadAddon(window.unicode);\n    `);\n  });\n\n  async function evalWidth(str: string): Promise<number> {\n    return ctx.page.evaluate(`window.term._core.unicodeService.getStringCellWidth('${str}')`);\n  }\n  const ourVersion = '15-graphemes';\n  test('wcwidth V15 emoji test', async () => {\n    // should have loaded '15-graphemes'\n    deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.versions`), ['6', '15', '15-graphemes']);\n    // switch should not throw\n    await ctx.page.evaluate(`window.term.unicode.activeVersion = '${ourVersion}';`);\n    strictEqual(await ctx.page.evaluate(`window.term.unicode.activeVersion`), ourVersion);\n    strictEqual(await evalWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'), 20,\n      '10 emoji - width 10 in V6; 20 in V11 or later');\n    strictEqual(await evalWidth('\\u{1F476}\\u{1F3FF}\\u{1F476}'), 4,\n      'baby with emoji modifier fitzpatrick type-6; baby');\n    strictEqual(await evalWidth('\\u{1F469}\\u200d\\u{1f469}\\u200d\\u{1f466}'), 2,\n      'woman+zwj+woman+zwj+boy');\n    strictEqual(await evalWidth('=\\u{1F3CB}\\u{FE0F}=\\u{F3CB}\\u{1F3FE}\\u200D\\u2640='), 7,\n      'person lifting weights (plain, emoji); woman lighting weights, medium dark');\n    strictEqual(await evalWidth('\\u{1F469}\\u{1F469}\\u{200D}\\u{1F393}\\u{1F468}\\u{1F3FF}\\u{200D}\\u{1F393}'), 6,\n      'woman; woman student; man student dark');\n    strictEqual(await evalWidth('\\u{1f1f3}\\u{1f1f4}/'), 3,\n      'regional indicator symbol letters N and O, cluster');\n    strictEqual(await evalWidth('\\u{1f1f3}/\\u{1f1f4}'), 3,\n      'regional indicator symbol letters N and O, separated');\n    strictEqual(await evalWidth('\\u0061\\u0301'), 1,\n      'letter a with acute accent');\n    strictEqual(await evalWidth('{\\u1100\\u1161\\u11a8\\u1100\\u1161}'), 6,\n      'Korean Jamo');\n    strictEqual(await evalWidth('\\uAC00=\\uD685='), 6,\n      'Hangul syllables (pre-composed)');\n    strictEqual(await evalWidth('(\\u26b0\\ufe0e)'), 3,\n      'coffin with text presentation');\n    strictEqual(await evalWidth('(\\u26b0\\ufe0f)'), 4,\n      'coffin with emoji presentation');\n    strictEqual(await evalWidth('<E\\u0301\\ufe0fg\\ufe0fa\\ufe0fl\\ufe0fi\\ufe0f\\ufe0ft\\ufe0fe\\u0301\\ufe0f>'), 16,\n      'Égalité (using separate acute) emoij_presentation');\n  });\n});\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" },\n    { \"path\": \"./benchmark\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/typings/addon-unicode-graphemes.d.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-unicode-graphemes' {\n  export class UnicodeGraphemesAddon implements ITerminalAddon {\n    constructor();\n    public activate(terminal: Terminal): void;\n    public dispose(): void;\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode-graphemes/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'UnicodeGraphemesAddon';\nconst mainFile = 'addon-unicode-graphemes.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      vs: path.resolve('../../out/vs')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-unicode11/.gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": "addons/addon-unicode11/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-unicode11/LICENSE",
    "content": "Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-unicode11/README.md",
    "content": "## @xterm/addon-unicode11\n\nAn addon providing Unicode version 11 rules for xterm.js.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-unicode11\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { Unicode11Addon } from '@xterm/addon-unicode11';\n\nconst terminal = new Terminal();\nconst unicode11Addon = new Unicode11Addon();\nterminal.loadAddon(unicode11Addon);\n\n// activate the new version\nterminal.unicode.activeVersion = '11';\n```\n"
  },
  {
    "path": "addons/addon-unicode11/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-unicode11\",\n  \"version\": \"0.9.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-unicode11.js\",\n  \"module\": \"lib/addon-unicode11.mjs\",\n  \"types\": \"typings/addon-unicode11.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode11/src/Unicode11Addon.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * UnicodeVersionProvider for V11.\n */\n\nimport type { Terminal, ITerminalAddon } from '@xterm/xterm';\nimport type { Unicode11Addon as IUnicode11Api } from '@xterm/addon-unicode11';\nimport { UnicodeV11 } from './UnicodeV11';\n\nexport class Unicode11Addon implements ITerminalAddon, IUnicode11Api {\n  public activate(terminal: Terminal): void {\n    terminal.unicode.register(new UnicodeV11());\n  }\n  public dispose(): void { }\n}\n"
  },
  {
    "path": "addons/addon-unicode11/src/UnicodeV11.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IUnicodeVersionProvider } from '@xterm/xterm';\nimport { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';\nimport { UnicodeService } from 'common/services/UnicodeService';\n\nconst BMP_COMBINING = [\n  [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],\n  [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5],\n  [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A],\n  [0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670],\n  [0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8],\n  [0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711],\n  [0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3],\n  [0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823],\n  [0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B],\n  [0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C],\n  [0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957],\n  [0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC],\n  [0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3],\n  [0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n  [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n  [0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75],\n  [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5],\n  [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3],\n  [0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n  [0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D],\n  [0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82],\n  [0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00],\n  [0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n  [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63],\n  [0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF],\n  [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3],\n  [0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44],\n  [0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA],\n  [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31],\n  [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1],\n  [0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19],\n  [0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39],\n  [0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87],\n  [0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6],\n  [0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A],\n  [0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060],\n  [0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086],\n  [0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF],\n  [0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734],\n  [0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5],\n  [0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3],\n  [0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886],\n  [0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928],\n  [0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18],\n  [0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E],\n  [0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C],\n  [0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE],\n  [0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A],\n  [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73],\n  [0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9],\n  [0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9],\n  [0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33],\n  [0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0],\n  [0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4],\n  [0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF],\n  [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064],\n  [0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1],\n  [0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D],\n  [0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D],\n  [0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802],\n  [0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826],\n  [0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF],\n  [0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982],\n  [0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD],\n  [0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32],\n  [0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C],\n  [0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4],\n  [0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1],\n  [0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5],\n  [0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E],\n  [0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF],\n  [0xFFF9, 0xFFFB]\n];\nconst HIGH_COMBINING = [\n  [0x101FD, 0x101FD], [0x102E0, 0x102E0],\n  [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06],\n  [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F],\n  [0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50],\n  [0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081],\n  [0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD],\n  [0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B],\n  [0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181],\n  [0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231],\n  [0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E],\n  [0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301],\n  [0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C],\n  [0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444],\n  [0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8],\n  [0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3],\n  [0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0],\n  [0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D],\n  [0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD],\n  [0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F],\n  [0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837],\n  [0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB],\n  [0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38],\n  [0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56],\n  [0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99],\n  [0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F],\n  [0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3],\n  [0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A],\n  [0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47],\n  [0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97],\n  [0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4],\n  [0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92],\n  [0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169],\n  [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n  [0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C],\n  [0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F],\n  [0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018],\n  [0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A],\n  [0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6],\n  [0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n  [0xE0100, 0xE01EF]\n];\nconst BMP_WIDE = [\n  [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A],\n  [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3],\n  [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653],\n  [0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1],\n  [0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5],\n  [0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA],\n  [0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA],\n  [0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B],\n  [0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E],\n  [0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797],\n  [0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C],\n  [0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99],\n  [0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB],\n  [0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096],\n  [0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E],\n  [0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E],\n  [0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C],\n  [0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3],\n  [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52],\n  [0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60],\n  [0xFFE0, 0xFFE6]\n];\nconst HIGH_WIDE = [\n  [0x16FE0, 0x16FE3], [0x17000, 0x187F7],\n  [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152],\n  [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004],\n  [0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A],\n  [0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248],\n  [0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320],\n  [0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393],\n  [0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0],\n  [0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440],\n  [0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E],\n  [0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596],\n  [0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5],\n  [0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5],\n  [0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB],\n  [0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2],\n  [0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF],\n  [0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82],\n  [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD]\n];\n\n// BMP lookup table, lazy initialized during first addon loading\nlet table: Uint8Array;\n\nfunction bisearch(ucs: number, data: number[][]): boolean {\n  let min = 0;\n  let max = data.length - 1;\n  let mid;\n  if (ucs < data[0][0] || ucs > data[max][1]) {\n    return false;\n  }\n  while (max >= min) {\n    mid = (min + max) >> 1;\n    if (ucs > data[mid][1]) {\n      min = mid + 1;\n    } else if (ucs < data[mid][0]) {\n      max = mid - 1;\n    } else {\n      return true;\n    }\n  }\n  return false;\n}\n\n\nexport class UnicodeV11 implements IUnicodeVersionProvider {\n  public readonly version = '11';\n\n  constructor() {\n    if (!table) {\n      table = new Uint8Array(65536);\n      table.fill(1);\n      table[0] = 0;\n      table.fill(0, 1, 32);\n      table.fill(0, 0x7f, 0xa0);\n      for (let r = 0; r < BMP_COMBINING.length; ++r) {\n        table.fill(0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1);\n      }\n      for (let r = 0; r < BMP_WIDE.length; ++r) {\n        table.fill(2, BMP_WIDE[r][0], BMP_WIDE[r][1] + 1);\n      }\n    }\n  }\n\n  public wcwidth(num: number): UnicodeCharWidth {\n    if (num < 32) return 0;\n    if (num < 127) return 1;\n    if (num < 65536) return table[num] as UnicodeCharWidth;\n    if (bisearch(num, HIGH_COMBINING)) return 0;\n    if (bisearch(num, HIGH_WIDE)) return 2;\n    return 1;\n  }\n\n  public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {\n    let width = this.wcwidth(codepoint);\n    let shouldJoin = width === 0 && preceding !== 0;\n    if (shouldJoin) {\n      const oldWidth = UnicodeService.extractWidth(preceding);\n      if (oldWidth === 0) {\n        shouldJoin = false;\n      } else if (oldWidth > width) {\n        width = oldWidth;\n      }\n    }\n    return UnicodeService.createPropertyValue(0, width, shouldJoin);\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode11/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"@xterm/addon-unicode11\": [\n        \"../typings/addon-unicode11.d.ts\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode11/test/Unicode11Addon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { deepStrictEqual } from 'assert';\nimport { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('Unicode11Addon', () => {\n\n  test.beforeEach(async () => {\n    await ctx.page.evaluate(`\n      window.term.reset()\n      window.unicode11?.dispose();\n      window.unicode11 = new Unicode11Addon();\n      window.term.loadAddon(window.unicode11);\n    `);\n  });\n\n  test('wcwidth V11 emoji test', async () => {\n    // should have loaded '11'\n    deepStrictEqual((await ctx.page.evaluate(`window.term.unicode.versions`) as string[]).includes('11'), true);\n    // switch should not throw\n    await ctx.page.evaluate(`window.term.unicode.activeVersion = '11';`);\n    deepStrictEqual(await ctx.page.evaluate(`window.term.unicode.activeVersion`), '11');\n    // v6: 10, V11: 20\n    deepStrictEqual(await ctx.page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20);\n  });\n});\n"
  },
  {
    "path": "addons/addon-unicode11/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-unicode11/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode11/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-unicode11/typings/addon-unicode11.d.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-unicode11' {\n  export class Unicode11Addon implements ITerminalAddon {\n    constructor();\n    public activate(terminal: Terminal): void;\n    public dispose(): void;\n  }\n}\n"
  },
  {
    "path": "addons/addon-unicode11/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'Unicode11Addon';\nconst mainFile = 'addon-unicode11.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      vs: path.resolve('../../out/vs')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-web-fonts/LICENSE",
    "content": "Copyright (c) 2024, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-web-fonts/README.md",
    "content": "## @xterm/addon-web-fonts\n\nAddon to use webfonts with [xterm.js](https://github.com/xtermjs/xterm.js).\nThis addon requires xterm.js v5+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-web-fonts\n```\n\n### Issue with Webfonts\n\nWebfonts are announced by CSS `font-face` rules (or its Javascript `FontFace` counterparts).\nSince font files tend to be quite big assets, browser engines often postpone their loading\nto an actual styling request of a codepoint matching a font file's `unicode-range`.\nIn short - font files will not be loaded until really needed.\n\nxterm.js on the other hand heavily relies on exact measurement of character glyphs\nto layout its output. This is done by determining the glyph width (DOM renderer) or\nby creating a glyph texture (WebGl renderer) for every output character.\nFor performance reasons both is done in synchronous code and cached.\nThis logic only works properly, if a font glyph is available on its first usage,\notherwise the browser will pick a glyph from a fallback font messing up the metrics.\n\nFor webfonts and xterm.js this means that we cannot rely on the default loading strategy\nof the browser, but have to preload the font files before using that font in xterm.js.\n\n\n### Static Preloading for the Rescue?\n\nIf you dont mind higher initial loading times with a white page shown,\nyou can tell the browser to preload the needed font files by placing the following\nlink elements in the document's head above any other CSS/Javascript:\n```html\n  <link rel=\"preload\" as=\"font\" href=\"/path/to/your/fontfile1.woff\" type=\"font/woff2\" crossorigin=\"anonymous\">\n  <link rel=\"preload\" as=\"font\" href=\"/path/to/your/fontfile2.woff\" type=\"font/woff2\" crossorigin=\"anonymous\">\n  ...\n  <!-- CSS with font-face rules matching the URLs above -->\n```\nBrowsers also will resort to system fonts, if the preloading takes too long.\nSo this solution has only a very limited scope.\n\n\n### Loading with WebFontsAddon\n\nThe webfonts addon offers several ways to deal with font assets loading\nwithout leaving the terminal in an unusable state.\n\nRecap - normally boostrapping of a new terminal involves these basic steps (Typescript):\n\n```typescript\nimport { Terminal } from '@xterm/xterm';\nimport { XYAddon } from '@xterm/addon-xy';\n\n// create a `Terminal` instance with some options, e.g. a custom font family\nconst terminal = new Terminal({fontFamily: 'monospace'});\n\n// create and load all addons you want to use, e.g. fit addon\nconst xyInstance = new XYAddon();\nterminal.loadAddon(xyInstance);\n\n// finally: call `open` of the terminal instance\nterminal.open(your_terminal_div_element);   // <-- critical path for webfonts\n// more boostrapping goes here ...\n```\nThis code is guaranteed to work in all browsers synchronously, as the identifier `monospace`\nwill always be available. It will also work synchronously with any installed system font,\nbut breaks horribly with webfonts. The actual culprit here is the call to `terminal.open`,\nwhich attaches the terminal to the DOM and starts the renderer with all the glyph caching\nmentioned above, while the webfont is not yet fully available.\n\nTo fix that, the webfonts addon provides a waiting condition (Typescript):\n```typescript\nimport { Terminal } from '@xterm/xterm';\nimport { XYAddon } from '@xterm/addon-xy';\nimport { WebFontsAddon } from '@xterm/addon-web-fonts';\n\n// create a `Terminal` instance, now with webfonts\nconst terminal = new Terminal({fontFamily: '\"Web Mono 1\", \"Super Powerline\", monospace'});\nconst xyInstance = new XYAddon();\nterminal.loadAddon(xyInstance);\n\nconst webFontsInstance = new WebFontsAddon();\nterminal.loadAddon(webFontsInstance);  \n\n// wait for webfonts to be fully loaded\nwebFontsInstance.loadFonts(['Web Mono 1', 'Super Powerline']).then(() => {\n  terminal.open(your_terminal_div_element);\n  // more boostrapping goes here ...\n});\n```\nHere `loadFonts` will look up the font face objects in `document.fonts`\nand load them before continuing. For this to work, you have to make sure,\nthat the CSS `font-face` rules for these webfonts are loaded beforehand,\notherwise `loadFonts` will not find the font family names (promise will be\nrejected for missing font family names).\n\nPlease note, that this cannot run synchronous anymore, so you will have to split your\nbootstrapping code into several stages. If that is too much of a hassle,\nyou can also move the whole bootstrapping under the waiting condition by using\nthe static loader instead (Typescript):\n```typescript\nimport { Terminal } from '@xterm/xterm';\nimport { XYAddon } from '@xterm/addon-xy';\n// import static loader\nimport { loadFonts } from '@xterm/addon-web-fonts';\n\nloadFonts(['Web Mono 1', 'Super Powerline']).then(() => {\n  // create a `Terminal` instance, now with webfonts\n  const terminal = new Terminal({fontFamily: '\"Web Mono 1\", \"Super Powerline\", monospace'});\n  const xyInstance = new XYAddon();\n  terminal.loadAddon(xyInstance);\n\n  // optional when using static loader\n  const webfontsInstance = new WebFontsAddon();\n  terminal.loadAddon(webfontsInstance);\n\n  terminal.open(your_terminal_div_element);\n  // more boostrapping goes here ...\n});\n```\nWith the static loader creating and loading of the actual addon can be omitted,\nas fonts are already loaded before any terminal setup happens.\n\n### Webfont Loading at Runtime\n\nGiven you have a terminal already running and want to change the font family\nto a different not yet loaded webfont:\n```typescript\n// either create font face objects in javascript\nconst ff1 = new FontFace('New Web Mono', url1, ...);\nconst ff2 = new FontFace('New Web Mono', url2, ...);\n// and await their loading\nloadFonts([ff1, ff2]).then(() => {\n  // apply new webfont to terminal\n  terminal.options.fontFamily = 'New Web Mono';\n  // since the new font might have slighly different metrics,\n  // also run the fit addon here (or any other custom resize logic)\n  fitAddon.fit();\n});\n\n// or alternatively use CSS to add new font-face rules, e.g.\ndocument.styleSheets[0].insertRule(\n  \"@font-face { font-family: 'New Web Mono'; src: url(newfont.woff); }\", 0);\n// and await the new font family name\nloadFonts(['New Web Mono']).then(() => {\n  // apply new webfont to terminal\n  terminal.options.fontFamily = 'New Web Mono';\n  // since the new font might have slighly different metrics,\n  // also run the fit addon here (or any other custom resize logic)\n  fitAddon.fit();\n});\n```\n\n### Forced Layout Update\n\nIf you have the addon loaded into your terminal, you can force the terminal to update\nthe layout with the method `WebFontsAddon.relayout`. This might come handy,\nif the terminal shows webfont related output issue for unknown reasons:\n```typescript\n...\n// given - terminal shows weird font issues, run:\nwebFontsInstance.relayout().then(() => {\n  // also run resize logic here, e.g. fit addon\n  fitAddon.fit();\n});\n```\nNote that this method is only meant as a quickfix on a running terminal to keep it\nin a working condition. A production-ready integration should never rely on it,\nbetter fix the  real root cause (most likely not properly awaiting the font loader\nhigher up in the code).\n\n\n### Webfonts from Fontsource\n\nThe addon has been tested to work with webfonts from fontsource.\nJavascript example for `vite` with ESM import:\n```javascript\nimport { Terminal } from '@xterm/xterm';\nimport { FitAddon } from '@xterm/addon-fit';\nimport { loadFonts } from '@xterm/addon-web-fonts';\nimport '@xterm/xterm/css/xterm.css';\nimport '@fontsource/roboto-mono';\nimport '@fontsource/roboto-mono/400.css';\nimport '@fontsource/roboto-mono/400-italic.css';\nimport '@fontsource/roboto-mono/700.css';\nimport '@fontsource/roboto-mono/700-italic.css';\n\nasync function main() {\n  let fontFamily = '\"Roboto Mono\", monospace';\n  try {\n    await loadFonts(['Roboto Mono']);\n  } catch (e) {\n    fontFamily = 'monospace';\n  }\n\n  const terminal = new Terminal({ fontFamily });\n  const fitAddon = new FitAddon();\n  terminal.loadAddon(fitAddon);\n  terminal.open(document.getElementById('your-xterm-container-div'));\n  fitAddon.fit();\n\n  // sync writing shows up in Roboto Mono w'o FOUT\n  // and a fallback to monospace\n  terminal.write('put any unicode char here');\n}\n\nmain();\n```\nThe fontsource packages download the font files to your project folder to be delivered\nfrom there later on. For security sensitive projects this should be the preferred way,\nas it brings the font files under your control.\n\nThe example furthermore contains proper exception handling with a fallback\n(skipped in all other examples for better readability).\n\n---\n\nAlso see the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-web-fonts/typings/addon-web-fonts.d.ts).\n"
  },
  {
    "path": "addons/addon-web-fonts/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-web-fonts\",\n  \"version\": \"0.1.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-web-fonts.js\",\n  \"module\": \"lib/addon-web-fonts.mjs\",\n  \"types\": \"typings/addon-web-fonts.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-fonts\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/src/WebFontsAddon.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, ITerminalAddon } from '@xterm/xterm';\nimport type { WebFontsAddon as IWebFontsApi } from '@xterm/addon-web-fonts';\n\n\n/**\n * Unquote a font family name.\n */\nfunction unquote(s: string): string {\n  if (s[0] === '\"' && s[s.length - 1] === '\"') return s.slice(1, -1);\n  if (s[0] === '\\'' && s[s.length - 1] === '\\'') return s.slice(1, -1);\n  return s;\n}\n\n\n/**\n * Quote a font family name conditionally.\n * @see https://mathiasbynens.be/notes/unquoted-font-family\n */\nfunction quote(s: string): string {\n  const pos = s.match(/([-_a-zA-Z0-9\\xA0-\\u{10FFFF}]+)/u);\n  const neg = s.match(/^(-?\\d|--)/m);\n  if (!neg && pos && pos[1] === s) return s;\n  return `\"${s.replace(/\"/g, '\\\\\"')}\"`;\n}\n\n\nfunction splitFamily(family: string | undefined): string[] {\n  if (!family) return [];\n  return family.split(',').map(e => unquote(e.trim()));\n}\n\n\nfunction createFamily(families: string[]): string {\n  return families.map(quote).join(', ');\n}\n\n\n/**\n * Hash a font face from it properties.\n * Used in `loadFonts` to avoid bloating\n * `document.fonts` from multiple calls.\n */\nfunction hashFontFace(ff: FontFace): string {\n  return JSON.stringify([\n    unquote(ff.family),\n    ff.stretch,\n    ff.style,\n    ff.unicodeRange,\n    ff.weight\n  ]);\n}\n\n\n/**\n * Wait for webfont resources to be loaded.\n *\n * Without any argument, all fonts currently listed in\n * `document.fonts` will be loaded.\n * For a more fine-grained loading strategy you can populate\n * the `fonts` argument with:\n * - font families      :   loads all fontfaces in `document.fonts`\n *                          matching the family names\n * - fontface objects   :   loads given fontfaces and adds them to\n *                          `document.fonts`\n *\n * The returned promise will resolve, when all loading is done.\n */\nfunction _loadFonts(fonts?: (string | FontFace)[]): Promise<FontFace[]> {\n  const ffs = Array.from(document.fonts);\n  if (!fonts || !fonts.length) {\n    return Promise.all(ffs.map(ff => ff.load()));\n  }\n  let toLoad: FontFace[] = [];\n  const ffsHashed = ffs.map(ff => hashFontFace(ff));\n  for (const font of fonts) {\n    if (font instanceof FontFace) {\n      const fontHashed = hashFontFace(font);\n      const idx = ffsHashed.indexOf(fontHashed);\n      if (idx === -1) {\n        document.fonts.add(font);\n        ffs.push(font);\n        ffsHashed.push(fontHashed);\n        toLoad.push(font);\n      } else {\n        toLoad.push(ffs[idx]);\n      }\n    } else {\n      // string as font\n      const familyFiltered = ffs.filter(ff => font === unquote(ff.family));\n      toLoad = toLoad.concat(familyFiltered);\n      if (!familyFiltered.length) {\n        return Promise.reject(`font family \"${font}\" not registered in document.fonts`);\n      }\n    }\n  }\n  return Promise.all(toLoad.map(ff => ff.load()));\n}\n\n\nexport function loadFonts(fonts?: (string | FontFace)[]): Promise<FontFace[]> {\n  return document.fonts.ready.then(() => _loadFonts(fonts));\n}\n\n\nexport class WebFontsAddon implements ITerminalAddon, IWebFontsApi {\n  private _term: Terminal | undefined;\n\n  constructor(public initialRelayout: boolean = true) { }\n\n  public dispose(): void {\n    this._term = undefined;\n  }\n\n  public activate(term: Terminal): void {\n    this._term = term;\n    if (this.initialRelayout) {\n      document.fonts.ready.then(() => this.relayout());\n    }\n  }\n\n  public loadFonts(fonts?: (string | FontFace)[]): Promise<FontFace[]> {\n    return loadFonts(fonts);\n  }\n\n  public async relayout(): Promise<void> {\n    if (!this._term) {\n      return;\n    }\n    await document.fonts.ready;\n    const family = this._term.options.fontFamily;\n    const families = splitFamily(family);\n    const webFamilies = Array.from(new Set(Array.from(document.fonts).map(e => unquote(e.family))));\n    const dirty: string[] = [];\n    const clean: string[] = [];\n    for (const fam of families) {\n      (webFamilies.indexOf(fam) !== -1 ? dirty : clean).push(fam);\n    }\n    if (!dirty.length) {\n      return;\n    }\n    await _loadFonts(dirty);\n    if (this._term) {\n      this._term.options.fontFamily = clean.length ? createFamily(clean) : 'monospace';\n      this._term.options.fontFamily = family;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\",\n      \"dom.iterable\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"@xterm/addon-web-fonts\": [\n        \"../typings/addon-web-fonts.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/test/WebFontsAddon.test.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport test from '@playwright/test';\nimport { deepStrictEqual, strictEqual } from 'assert';\nimport { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx, { cols: 40 });\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('WebFontsAddon', () => {\n\n  test.beforeEach(async () => {\n    // make sure that we start with no webfonts in the document\n    const empty = await await getDocumentFonts();\n    deepStrictEqual(empty, []);\n  });\n  test.afterEach(async () => {\n    // for font loading tests to work, we have to remove added rules and fonts\n    // to work around the quite aggressive font caching done by the browsers\n    await ctx.page.evaluate(`\n      document.styleSheets[0].deleteRule(1);\n      document.styleSheets[0].deleteRule(0);\n      document.fonts.clear();\n    `);\n  });\n\n  test.describe('font loading at runtime', () => {\n    test('loadFonts (JS)', async () => {\n      await ctx.page.evaluate(`\n        const ff1 = new FontFace('Kongtext', \"url(/kongtext.regular.ttf) format('truetype')\");\n        const ff2 = new FontFace('BPdots', \"url(/bpdots.regular.otf) format('opentype')\");\n        loadFonts([ff1, ff2]);\n      `);\n      deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);\n    });\n    test('loadFonts (CSS, unquoted)', async () => {\n      await ctx.page.evaluate(`\n        document.styleSheets[0].insertRule(\"@font-face {font-family: Kongtext; src: url(/kongtext.regular.ttf) format('truetype')}\", 0);\n        document.styleSheets[0].insertRule(\"@font-face {font-family: BPdots; src: url(/bpdots.regular.otf) format('opentype')}\", 1);\n        loadFonts(['Kongtext', 'BPdots']);\n      `);\n      deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);\n    });\n    test('loadFonts (CSS, quoted)', async ({ browser }) => {\n      // NOTE: firefox preserves family quotes from CSS rules in fontface, all other browsers unquote them\n      await ctx.page.evaluate(`\n        document.styleSheets[0].insertRule(\"@font-face {font-family: 'Kongtext'; src: url(/kongtext.regular.ttf) format('truetype')}\", 0);\n        document.styleSheets[0].insertRule(\"@font-face {font-family: 'BPdots'; src: url(/bpdots.regular.otf) format('opentype')}\", 1);\n        loadFonts(['Kongtext', 'BPdots']);\n      `);\n      if (browser.browserType().name() === 'firefox') {\n        deepStrictEqual(await getDocumentFonts(), [{ family: '\"Kongtext\"', status: 'loaded' }, { family: '\"BPdots\"', status: 'loaded' }]);\n      } else {\n        deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);\n      }\n    });\n    test('FontFace hashing', async () => {\n      // multiple calls of `loadFonts` with the same objects shall not bloat document.fonts\n      await ctx.page.evaluate(`\n        const ff1 = new FontFace('Kongtext', \"url(/kongtext.regular.ttf) format('truetype')\");\n        const ff2 = new FontFace('BPdots', \"url(/bpdots.regular.otf) format('opentype')\");\n        loadFonts([ff1, ff2]);\n        loadFonts([ff1, ff2]);\n        loadFonts([ff1, ff2]).then(() => loadFonts([ff1, ff2]));\n      `);\n      deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);\n    });\n\n    test('autoload & relayout from ctor', async ({ browser }) => {\n      // to make this test work, we exclude the default measurement char W (x57) by restricting unicode-range\n      // now the browser will postpone font loading until codepoint is hit --> wrong glyph metrics on first usage\n      const data = await ctx.page.evaluate(`\n          document.styleSheets[0].insertRule(\"@font-face {font-family: Kongtext; src: url(/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}\", 0);\n        `);\n      deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);\n\n      // broken case: webfont in ctor without addon usage\n      await ctx.page.evaluate(`\n          window.helperTerm = new Terminal({fontFamily: '\"Kongtext\", ' + term.options.fontFamily});\n          window.helperTerm.open(term.element);\n        `);\n\n      // safari loads the font, firefox & chrome dont\n      if (browser.browserType().name() === 'webkit') {\n        deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);\n      } else {\n        deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);\n      }\n\n      // good case: addon fixes layout for webfont in ctor\n      // the relayout happens async, so wait a bit with a promise\n      await ctx.page.evaluate(`\n          window.helperTerm.dispose();\n          window.helperTerm = new Terminal({fontFamily: '\"Kongtext\", ' + term.options.fontFamily});\n          window._webfontsAddon = new WebFontsAddon();\n          window.helperTerm.loadAddon(window._webfontsAddon);\n          window.helperTerm.open(term.element);\n        `);\n      await timeout(100);\n      deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);\n\n      // cleanup this messy test case\n      await ctx.page.evaluate(`\n          window.helperTerm.dispose();\n          window._webfontsAddon.dispose();\n        `);\n    });\n  });\n\n});\n\nasync function getDocumentFonts(): Promise<any> {\n  return ctx.page.evaluate(`Array.from(document.fonts).map(ff => ({family: ff.family, status: ff.status}))`);\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-web-fonts/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    // \"downlevelIteration\": true,\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/typings/addon-web-fonts.d.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n\nimport { Terminal, ITerminalAddon } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-web-fonts' {\n\n  /**\n   * Addon to use webfonts in xterm.js\n   */\n  export class WebFontsAddon implements ITerminalAddon {\n    /**\n     * @param initialRelayout Force initial relayout, if a webfont was found (default true).\n     */\n    constructor(initialRelayout?: boolean);\n    public activate(terminal: Terminal): void;\n    public dispose(): void;\n\n    /**\n     * Wait for webfont resources to be loaded.\n     *\n     * Without any argument, all fonts currently listed in\n     * `document.fonts` will be loaded.\n     * For a more fine-grained loading strategy you can populate\n     * the `fonts` argument with:\n     * - font families      :   loads all fontfaces in `document.fonts`\n     *                          matching the family names\n     * - fontface objects   :   loads given fontfaces and adds them to\n     *                          `document.fonts`\n     *\n     * The returned promise will resolve, when all loading is done.\n     */\n    public loadFonts(fonts?: (string | FontFace)[]): Promise<FontFace[]>;\n\n    /**\n     * Force a terminal relayout by altering `options.FontFamily`.\n     *\n     * Found webfonts in `fontFamily` are temporarily removed until the webfont\n     * resources are fully loaded.\n     * \n     * Call this method, if a terminal with webfonts is stuck with broken\n     * glyph metrics.\n     * \n     * The returned promise will resolve, when font loading and layouting are done.\n     */\n    public relayout(): Promise<void>;\n  }\n\n  /**\n   * Wait for webfont resources to be loaded.\n   *\n   * Without any argument, all fonts currently listed in\n   * `document.fonts` will be loaded.\n   * For a more fine-grained loading strategy you can populate\n   * the `fonts` argument with:\n   * - font families      :   loads all fontfaces in `document.fonts`\n   *                          matching the family names\n   * - fontface objects   :   loads given fontfaces and adds them to\n   *                          `document.fonts`\n   *\n   * The returned promise will resolve, when all loading is done.\n   */\n  function loadFonts(fonts?: (string | FontFace)[]): Promise<FontFace[]>;\n}\n"
  },
  {
    "path": "addons/addon-web-fonts/webpack.config.js",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'WebFontsAddon';\nconst mainFile = 'addon-web-fonts.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-web-links/.gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": "addons/addon-web-links/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-web-links/LICENSE",
    "content": "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-web-links/README.md",
    "content": "## @xterm/addon-web-links\n\nAn addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables web links. This addon requires xterm.js v4+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-web-links\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { WebLinksAddon } from '@xterm/addon-web-links';\n\nconst terminal = new Terminal();\nterminal.loadAddon(new WebLinksAddon());\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-web-links/typings/addon-web-links.d.ts) for more advanced usage.\n"
  },
  {
    "path": "addons/addon-web-links/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-web-links\",\n  \"version\": \"0.12.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-web-links.js\",\n  \"module\": \"lib/addon-web-links.mjs\",\n  \"types\": \"typings/addon-web-links.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-web-links/src/WebLinkProvider.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ILinkProvider, ILink, Terminal, IViewportRange, IBufferLine } from '@xterm/xterm';\n\nexport interface ILinkProviderOptions {\n  hover?(event: MouseEvent, text: string, location: IViewportRange): void;\n  leave?(event: MouseEvent, text: string): void;\n  urlRegex?: RegExp;\n}\n\nexport class WebLinkProvider implements ILinkProvider {\n\n  constructor(\n    private readonly _terminal: Terminal,\n    private readonly _regex: RegExp,\n    private readonly _handler: (event: MouseEvent, uri: string) => void,\n    private readonly _options: ILinkProviderOptions = {}\n  ) {\n\n  }\n\n  public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {\n    const links = LinkComputer.computeLink(y, this._regex, this._terminal, this._handler);\n    callback(this._addCallbacks(links));\n  }\n\n  private _addCallbacks(links: ILink[]): ILink[] {\n    return links.map(link => {\n      link.leave = this._options.leave;\n      link.hover = (event: MouseEvent, uri: string): void => {\n        if (this._options.hover) {\n          const { range } = link;\n          this._options.hover(event, uri, range);\n        }\n      };\n      return link;\n    });\n  }\n}\n\nfunction isUrl(urlString: string): boolean {\n  try {\n    const url = new URL(urlString);\n    const parsedBase = url.password && url.username\n      ? `${url.protocol}//${url.username}:${url.password}@${url.host}`\n      : url.username\n        ? `${url.protocol}//${url.username}@${url.host}`\n        : `${url.protocol}//${url.host}`;\n    return urlString.toLocaleLowerCase().startsWith(parsedBase.toLocaleLowerCase());\n  } catch {\n    return false;\n  }\n}\n\nexport class LinkComputer {\n  public static computeLink(y: number, regex: RegExp, terminal: Terminal, activate: (event: MouseEvent, uri: string) => void): ILink[] {\n    const rex = new RegExp(regex.source, (regex.flags || '') + 'g');\n\n    const [lines, startLineIndex] = LinkComputer._getWindowedLineStrings(y - 1, terminal);\n    const line = lines.join('');\n\n    let match;\n    const result: ILink[] = [];\n\n    while (match = rex.exec(line)) {\n      const text = match[0];\n\n      // check via URL if the matched text would form a proper url\n      if (!isUrl(text)) {\n        continue;\n      }\n\n      // map string positions back to buffer positions\n      // values are 0-based right side excluding\n      const [startY, startX] = LinkComputer._mapStrIdx(terminal, startLineIndex, 0, match.index);\n      const [endY, endX] = LinkComputer._mapStrIdx(terminal, startY, startX, text.length);\n\n      if (startY === -1 || startX === -1 || endY === -1 || endX === -1) {\n        continue;\n      }\n\n      // range expects values 1-based right side including, thus +1 except for endX\n      const range = {\n        start: {\n          x: startX + 1,\n          y: startY + 1\n        },\n        end: {\n          x: endX,\n          y: endY + 1\n        }\n      };\n\n      result.push({ range, text, activate });\n    }\n\n    return result;\n  }\n\n  /**\n   * Get wrapped content lines for the current line index.\n   * The top/bottom line expansion stops at whitespaces or length > 2048.\n   * Returns an array with line strings and the top line index.\n   *\n   * NOTE: We pull line strings with trimRight=true on purpose to make sure\n   * to correctly match urls with early wrapped wide chars. This corrupts the string index\n   * for 1:1 backmapping to buffer positions, thus needs an additional correction in _mapStrIdx.\n   */\n  private static _getWindowedLineStrings(lineIndex: number, terminal: Terminal): [string[], number] {\n    let line: IBufferLine | undefined;\n    let topIdx = lineIndex;\n    let bottomIdx = lineIndex;\n    let length = 0;\n    let content = '';\n    const lines: string[] = [];\n\n    if ((line = terminal.buffer.active.getLine(lineIndex))) {\n      const currentContent = line.translateToString(true);\n\n      // expand top, stop on whitespaces or length > 2048\n      if (line.isWrapped && currentContent[0] !== ' ') {\n        length = 0;\n        while ((line = terminal.buffer.active.getLine(--topIdx)) && length < 2048) {\n          content = line.translateToString(true);\n          length += content.length;\n          lines.push(content);\n          if (!line.isWrapped || content.indexOf(' ') !== -1) {\n            break;\n          }\n        }\n        lines.reverse();\n      }\n\n      // append current line\n      lines.push(currentContent);\n\n      // expand bottom, stop on whitespaces or length > 2048\n      length = 0;\n      while ((line = terminal.buffer.active.getLine(++bottomIdx)) && line.isWrapped && length < 2048) {\n        content = line.translateToString(true);\n        length += content.length;\n        lines.push(content);\n        if (content.indexOf(' ') !== -1) {\n          break;\n        }\n      }\n    }\n    return [lines, topIdx];\n  }\n\n  /**\n   * Map a string index back to buffer positions.\n   * Returns buffer position as [lineIndex, columnIndex] 0-based,\n   * or [-1, -1] in case the lookup ran into a non-existing line.\n   */\n  private static _mapStrIdx(terminal: Terminal, lineIndex: number, rowIndex: number, stringIndex: number): [number, number] {\n    const buf = terminal.buffer.active;\n    const cell = buf.getNullCell();\n    let start = rowIndex;\n    while (stringIndex) {\n      const line = buf.getLine(lineIndex);\n      if (!line) {\n        return [-1, -1];\n      }\n      for (let i = start; i < line.length; ++i) {\n        line.getCell(i, cell);\n        const chars = cell.getChars();\n        const width = cell.getWidth();\n        if (width) {\n          stringIndex -= chars.length || 1;\n\n          // correct stringIndex for early wrapped wide chars:\n          // - currently only happens at last cell\n          // - cells to the right are reset with chars='' and width=1 in InputHandler.print\n          // - follow-up line must be wrapped and contain wide char at first cell\n          // --> if all these conditions are met, correct stringIndex by +1\n          if (i === line.length - 1 && chars === '') {\n            const line = buf.getLine(lineIndex + 1);\n            if (line && line.isWrapped) {\n              line.getCell(0, cell);\n              if (cell.getWidth() === 2) {\n                stringIndex += 1;\n              }\n            }\n          }\n        }\n        if (stringIndex < 0) {\n          return [lineIndex, i];\n        }\n      }\n      lineIndex++;\n      start = 0;\n    }\n    return [lineIndex, start];\n  }\n}\n"
  },
  {
    "path": "addons/addon-web-links/src/WebLinksAddon.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';\nimport type { WebLinksAddon as IWebLinksApi } from '@xterm/addon-web-links';\nimport { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';\n\n// consider everthing starting with http:// or https://\n// up to first whitespace, `\"` or `'` as url\n// NOTE: The repeated end clause is needed to not match a dangling `:`\n// resembling the old (...)*([^:\"\\'\\\\s]) final path clause\n// additionally exclude early + final:\n// - unsafe from rfc3986: !*'()\n// - unsafe chars from rfc1738: {}|\\^~[]` (minus [] as we need them for ipv6 adresses, also allow ~)\n// also exclude as finals:\n// - final interpunction like ,.!?\n// - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)\n// - unsafe chars from rfc1738: {}|\\^~[]`\nconst strictUrlRegex = /(https?|HTTPS?):[/]{2}[^\\s\"'!*(){}|\\\\\\^<>`]*[^\\s\"':,.!?{}|\\\\\\^~\\[\\]`()<>]/;\n\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n  const newWindow = window.open();\n  if (newWindow) {\n    try {\n      newWindow.opener = null;\n    } catch {\n      // no-op, Electron can throw\n    }\n    newWindow.location.href = uri;\n  } else {\n    console.warn('Opening link blocked as opener could not be cleared');\n  }\n}\n\nexport class WebLinksAddon implements ITerminalAddon, IWebLinksApi {\n  private _terminal: Terminal | undefined;\n  private _linkProvider: IDisposable | undefined;\n\n  constructor(\n    private _handler: (event: MouseEvent, uri: string) => void = handleLink,\n    private _options: ILinkProviderOptions = {}\n  ) {\n  }\n\n  public activate(terminal: Terminal): void {\n    this._terminal = terminal;\n    const options = this._options as ILinkProviderOptions;\n    const regex = options.urlRegex ?? strictUrlRegex;\n    this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options));\n  }\n\n  public dispose(): void {\n    this._linkProvider?.dispose();\n  }\n}\n"
  },
  {
    "path": "addons/addon-web-links/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2015\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"@xterm/addon-web-links\": [\n        \"../typings/addon-web-links.d.ts\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/addon-web-links/test/WebLinksAddon.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport test from '@playwright/test';\nimport { deepStrictEqual, strictEqual } from 'assert';\nimport { readFile } from 'fs';\nimport { resolve } from 'path';\nimport { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';\n\ninterface ILinkStateData {\n  uri?: string;\n  range?: {\n    start: {\n      x: number;\n      y: number;\n    };\n    end: {\n      x: number;\n      y: number;\n    };\n  };\n}\n\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx, { cols: 40 });\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('WebLinksAddon', () => {\n\n  test.beforeEach(async () => {\n    await ctx.page.evaluate(`\n      window.term.reset();\n      window._linkaddon?.dispose();\n    `);\n    await timeout(10);\n    await ctx.page.evaluate(`\n      window._linkaddon = new WebLinksAddon();\n      window.term.loadAddon(window._linkaddon);\n    `);\n  });\n\n  const countryTlds = [\n    '.ac', '.ad', '.ae', '.af', '.ag', '.ai', '.al', '.am', '.ao', '.aq', '.ar', '.as', '.at',\n    '.au', '.aw', '.ax', '.az', '.ba', '.bb', '.bd', '.be', '.bf', '.bg', '.bh', '.bi', '.bj',\n    '.bm', '.bn', '.bo', '.bq', '.br', '.bs', '.bt', '.bw', '.by', '.bz', '.ca', '.cc', '.cd',\n    '.cf', '.cg', '.ch', '.ci', '.ck', '.cl', '.cm', '.cn', '.co', '.cr', '.cu', '.cv', '.cw',\n    '.cx', '.cy', '.cz', '.de', '.dj', '.dk', '.dm', '.do', '.dz', '.ec', '.ee', '.eg', '.eh',\n    '.er', '.es', '.et', '.eu', '.fi', '.fj', '.fk', '.fm', '.fo', '.fr', '.ga', '.gd', '.ge',\n    '.gf', '.gg', '.gh', '.gi', '.gl', '.gm', '.gn', '.gp', '.gq', '.gr', '.gs', '.gt', '.gu',\n    '.gw', '.gy', '.hk', '.hm', '.hn', '.hr', '.ht', '.hu', '.id', '.ie', '.il', '.im', '.in',\n    '.io', '.iq', '.ir', '.is', '.it', '.je', '.jm', '.jo', '.jp', '.ke', '.kg', '.kh', '.ki',\n    '.km', '.kn', '.kp', '.kr', '.kw', '.ky', '.kz', '.la', '.lb', '.lc', '.li', '.lk', '.lr',\n    '.ls', '.lt', '.lu', '.lv', '.ly', '.ma', '.mc', '.md', '.me', '.mg', '.mh', '.mk', '.ml',\n    '.mm', '.mn', '.mo', '.mp', '.mq', '.mr', '.ms', '.mt', '.mu', '.mv', '.mw', '.mx', '.my',\n    '.mz', '.na', '.nc', '.ne', '.nf', '.ng', '.ni', '.nl', '.no', '.np', '.nr', '.nu', '.nz',\n    '.om', '.pa', '.pe', '.pf', '.pg', '.ph', '.pk', '.pl', '.pm', '.pn', '.pr', '.ps', '.pt',\n    '.pw', '.py', '.qa', '.re', '.ro', '.rs', '.ru', '.rw', '.sa', '.sb', '.sc', '.sd', '.se',\n    '.sg', '.sh', '.si', '.sk', '.sl', '.sm', '.sn', '.so', '.sr', '.ss', '.st', '.su', '.sv',\n    '.sx', '.sy', '.sz', '.tc', '.td', '.tf', '.tg', '.th', '.tj', '.tk', '.tl', '.tm', '.tn',\n    '.to', '.tr', '.tt', '.tv', '.tw', '.tz', '.ua', '.ug', '.uk', '.us', '.uy', '.uz', '.va',\n    '.vc', '.ve', '.vg', '.vi', '.vn', '.vu', '.wf', '.ws', '.ye', '.yt', '.za', '.zm', '.zw'\n  ];\n  for (const tld of countryTlds) {\n    test(tld, async () => await testHostName(`foo${tld}`));\n  }\n  test(`.com`, async () => await testHostName(`foo.com`));\n  for (const tld of countryTlds) {\n    test(`.com${tld}`, async () => await testHostName(`foo.com${tld}`));\n  }\n\n  test.describe('correct buffer offsets & uri', () => {\n    test.beforeEach(async () => {\n      await ctx.page.evaluate(`\n        window._linkStateData = {uri:''};\n        window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; };\n      `);\n    });\n    test('all half width', async () => {\n      await ctx.proxy.write('aaa http://example.com aaa http://example.com aaa');\n      await resetAndHover(5, 0);\n      await evalLinkStateData('http://example.com', { start: { x: 5, y: 1 }, end: { x: 22, y: 1 } });\n      await resetAndHover(1, 1);\n      await evalLinkStateData('http://example.com', { start: { x: 28, y: 1 }, end: { x: 5, y: 2 } });\n    });\n    test('url after full width', async () => {\n      await ctx.proxy.write('￥￥￥ http://example.com ￥￥￥ http://example.com aaa');\n      await resetAndHover(8, 0);\n      await evalLinkStateData('http://example.com', { start: { x: 8, y: 1 }, end: { x: 25, y: 1 } });\n      await resetAndHover(1, 1);\n      await evalLinkStateData('http://example.com', { start: { x: 34, y: 1 }, end: { x: 11, y: 2 } });\n    });\n    test('full width within url and before', async () => {\n      await ctx.proxy.write('￥￥￥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ￥￥￥');\n      await resetAndHover(8, 0);\n      await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } });\n      await resetAndHover(1, 1);\n      await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } });\n      await resetAndHover(17, 1);\n      await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 17, y: 2 }, end: { x: 19, y: 3 } });\n    });\n    test('name + password url after full width and combining', async () => {\n      await ctx.proxy.write('￥￥￥cafe\\u0301 http://test:password@example.com/some_path');\n      await resetAndHover(12, 0);\n      await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });\n      await resetAndHover(5, 1);\n      await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } });\n    });\n    test('url encoded params work properly', async () => {\n      await ctx.proxy.write('￥￥￥cafe\\u0301 http://test:password@example.com/some_path?param=1%202%3');\n      await resetAndHover(12, 0);\n      await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } });\n      await resetAndHover(5, 1);\n      await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } });\n    });\n  });\n\n  // issue #4964\n  test('uppercase in protocol and host, default ports', async () => {\n    await ctx.proxy.write(\n      `  HTTP://EXAMPLE.COM  \\r\\n` +\n      `  HTTPS://Example.com  \\r\\n` +\n      `  HTTP://Example.com:80  \\r\\n` +\n      `  HTTP://Example.com:80/staysUpper  \\r\\n` +\n      `  HTTP://Ab:xY@abc.com:80/staysUpper  \\r\\n`\n    );\n    await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`);\n    await pollForLinkAtCell(3, 1, `HTTPS://Example.com`);\n    await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`);\n    await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`);\n    await pollForLinkAtCell(3, 4, `HTTP://Ab:xY@abc.com:80/staysUpper`);\n  });\n});\n\nasync function testHostName(hostname: string): Promise<void> {\n  await ctx.proxy.write(\n    `  http://${hostname}  \\r\\n` +\n    `  http://${hostname}/a~b#c~d?e~f  \\r\\n` +\n    `  http://${hostname}/colon:test  \\r\\n` +\n    `  http://${hostname}/colon:test:  \\r\\n` +\n    `\"http://${hostname}/\"\\r\\n` +\n    `\\'http://${hostname}/\\'\\r\\n` +\n    `http://${hostname}/subpath/+/id`\n  );\n  await pollForLinkAtCell(3, 0, `http://${hostname}`);\n  await pollForLinkAtCell(3, 1, `http://${hostname}/a~b#c~d?e~f`);\n  await pollForLinkAtCell(3, 2, `http://${hostname}/colon:test`);\n  await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`);\n  await pollForLinkAtCell(2, 4, `http://${hostname}/`);\n  await pollForLinkAtCell(2, 5, `http://${hostname}/`);\n  await pollForLinkAtCell(1, 6, `http://${hostname}/subpath/+/id`);\n}\n\nasync function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {\n  await ctx.page.mouse.move(...(await cellPos(col, row)));\n  await pollFor(ctx.page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true);\n  const text = await ctx.page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`);\n  deepStrictEqual(text, value);\n}\n\nasync function resetAndHover(col: number, row: number): Promise<void> {\n  await ctx.page.mouse.move(0, 0);\n  await ctx.page.evaluate(`window._linkStateData = {uri:''};`);\n  await new Promise(r => setTimeout(r, 200));\n  await ctx.page.mouse.move(...(await cellPos(col, row)));\n  await pollFor(ctx.page, `!!window._linkStateData.uri.length`, true);\n}\n\nasync function evalLinkStateData(uri: string, range: any): Promise<void> {\n  const data: ILinkStateData = await ctx.page.evaluate(`window._linkStateData`);\n  strictEqual(data.uri, uri);\n  deepStrictEqual(data.range, range);\n}\n\nasync function cellPos(col: number, row: number): Promise<[number, number]> {\n  const coords: any = await ctx.page.evaluate(`\n    (function() {\n      const rect = window.term.element.getBoundingClientRect();\n      const dim = window.term.dimensions;\n      return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right, width: dim.css.cell.width, height: dim.css.cell.height};\n    })();\n  `);\n  return [col * coords.width + coords.left + 2, row * coords.height + coords.top + 2];\n}\n"
  },
  {
    "path": "addons/addon-web-links/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-web-links/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    // \"downlevelIteration\": true,\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-web-links/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-web-links/typings/addon-web-links.d.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n\nimport { Terminal, ITerminalAddon, IViewportRange } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-web-links' {\n  /**\n   * An xterm.js addon that enables web links.\n   */\n  export class WebLinksAddon implements ITerminalAddon {\n    /**\n     * Creates a new web links addon.\n     * @param handler The callback when the link is called.\n     *\n     * Note that this may not work when the terminal is hosted inside an iframe,\n     * in that case provide a custom handler in that case being mindful of\n     * possible security issues like reverse tabnapping.\n     * @param options Options for the link provider.\n     */\n    constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkProviderOptions);\n\n    /**\n     * Activates the addon\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n  }\n\n  /**\n   * An object containing options for a link provider.\n   */\n  export interface ILinkProviderOptions {\n    /**\n     * A callback that fires when the mouse hovers over a link.\n     */\n    hover?(event: MouseEvent, text: string, location: IViewportRange): void;\n\n    /**\n     * A callback that fires when the mouse leaves a link. Note that this can\n     * happen even when tooltipCallback hasn't fired for the link yet.\n     */\n    leave?(event: MouseEvent, text: string): void;\n\n    /**\n     * A callback to use instead of the default one.\n    */\n    urlRegex?: RegExp;\n  }\n}\n"
  },
  {
    "path": "addons/addon-web-links/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'WebLinksAddon';\nconst mainFile = 'addon-web-links.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "addons/addon-webgl/.gitignore",
    "content": "lib\nnode_modules"
  },
  {
    "path": "addons/addon-webgl/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib/\n!lib/**/*.d.ts\n\n!lib/**/*.js\n!lib/**/*.js.map\n\n!lib/**/*.mjs\n!lib/**/*.mjs.map\n\n!lib/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "addons/addon-webgl/LICENSE",
    "content": "Copyright (c) 2018, The xterm.js authors (https://github.com/xtermjs/xterm.js)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "addons/addon-webgl/README.md",
    "content": "## @xterm/addon-webgl\n\nAn addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL2-based renderer. This addon requires xterm.js v4+.\n\n### Install\n\n```bash\nnpm install --save @xterm/addon-webgl\n```\n\n### Usage\n\n```ts\nimport { Terminal } from '@xterm/xterm';\nimport { WebglAddon } from '@xterm/addon-webgl';\n\nconst terminal = new Terminal();\nterminal.open(element);\nterminal.loadAddon(new WebglAddon());\n```\n\nSee the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-webgl/typings/addon-webgl.d.ts) for more advanced usage.\n\n### Handling Context Loss\n\nThe browser may drop WebGL contexts for various reasons like OOM or after the system has been suspended. There is an API exposed that fires the `webglcontextlost` event fired on the canvas so embedders can handle it however they wish. An easy, but suboptimal way, to handle this is by disposing of WebglAddon when the event fires:\n\n```ts\nconst terminal = new Terminal();\nconst addon = new WebglAddon();\naddon.onContextLoss(e => {\n  addon.dispose();\n});\nterminal.loadAddon(addon);\n```\n\nRead more about handling WebGL context losses on the [Khronos wiki](https://www.khronos.org/webgl/wiki/HandlingContextLost).\n"
  },
  {
    "path": "addons/addon-webgl/package.json",
    "content": "{\n  \"name\": \"@xterm/addon-webgl\",\n  \"version\": \"0.19.0\",\n  \"author\": {\n    \"name\": \"The xterm.js authors\",\n    \"url\": \"https://xtermjs.org/\"\n  },\n  \"main\": \"lib/addon-webgl.js\",\n  \"module\": \"lib/addon-webgl.mjs\",\n  \"types\": \"typings/addon-webgl.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"terminal\",\n    \"webgl\",\n    \"xterm\",\n    \"xterm.js\"\n  ],\n  \"scripts\": {\n    \"build\": \"../../node_modules/.bin/tsgo -p .\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"../../node_modules/.bin/webpack\",\n    \"prepublishOnly\": \"npm run package\",\n    \"start\": \"node ../../demo/start\"\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/CellColorResolver.ts",
    "content": "import { ISelectionRenderModel } from 'browser/renderer/shared/Types';\nimport { ICoreBrowserService, IThemeService } from 'browser/services/Services';\nimport { ReadonlyColorSet } from 'browser/Types';\nimport { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';\nimport { IDecorationService, IOptionsService } from 'common/services/Services';\nimport { ICellData } from 'common/Types';\nimport { Terminal } from '@xterm/xterm';\nimport { rgba } from 'common/Color';\nimport { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';\nimport { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';\n\n// Work variables to avoid garbage collection\nlet $fg = 0;\nlet $bg = 0;\nlet $hasFg = false;\nlet $hasBg = false;\nlet $isSelected = false;\nlet $colors: ReadonlyColorSet | undefined;\nlet $variantOffset = 0;\n\nexport class CellColorResolver {\n  /**\n   * The shared result of the {@link resolve} call. This is only safe to use immediately after as\n   * any other calls will share object.\n   */\n  public readonly result: { fg: number, bg: number, ext: number } = {\n    fg: 0,\n    bg: 0,\n    ext: 0\n  };\n\n  constructor(\n    private readonly _terminal: Terminal,\n    private readonly _optionService: IOptionsService,\n    private readonly _selectionRenderModel: ISelectionRenderModel,\n    private readonly _decorationService: IDecorationService,\n    private readonly _coreBrowserService: ICoreBrowserService,\n    private readonly _themeService: IThemeService\n  ) {\n  }\n\n  /**\n   * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves\n   * overrides, inverse and selection for the cell which can then be used to feed into the renderer.\n   */\n  public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number, deviceCellHeight: number): void {\n    this.result.bg = cell.bg;\n    this.result.fg = cell.fg;\n    this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;\n    // Get any foreground/background overrides, this happens on the model to avoid spreading\n    // override logic throughout the different sub-renderers\n\n    // Reset overrides work variables\n    $bg = 0;\n    $fg = 0;\n    $hasBg = false;\n    $hasFg = false;\n    $isSelected = false;\n    $colors = this._themeService.colors;\n    $variantOffset = 0;\n\n    const code = cell.getCode();\n    if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) {\n      const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15));\n      $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2);\n    }\n    if ($variantOffset === 0 && blockPatternCodepoints.has(code)) {\n      $variantOffset = ((x * deviceCellWidth) % 2) * 2 + ((y * deviceCellHeight) % 2);\n    }\n    // Apply decorations on the bottom layer\n    this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {\n      if (d.backgroundColorRGB) {\n        $bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;\n        $hasBg = true;\n      }\n      if (d.foregroundColorRGB) {\n        $fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;\n        $hasFg = true;\n      }\n    });\n\n    // Apply the selection color if needed\n    $isSelected = this._selectionRenderModel.isCellSelected(this._terminal, x, y);\n    if ($isSelected) {\n      // If the cell has a bg color, retain the color by blending it with the selection color\n      if (\n        (this.result.fg & FgFlags.INVERSE) ||\n        (this.result.bg & Attributes.CM_MASK) !== Attributes.CM_DEFAULT\n      ) {\n        // Resolve the standard bg color\n        if (this.result.fg & FgFlags.INVERSE) {\n          switch (this.result.fg & Attributes.CM_MASK) {\n            case Attributes.CM_P16:\n            case Attributes.CM_P256:\n              $bg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba;\n              break;\n            case Attributes.CM_RGB:\n              $bg = ((this.result.fg & Attributes.RGB_MASK) << 8) | 0xFF;\n              break;\n            case Attributes.CM_DEFAULT:\n            default:\n              $bg = this._themeService.colors.foreground.rgba;\n          }\n        } else {\n          switch (this.result.bg & Attributes.CM_MASK) {\n            case Attributes.CM_P16:\n            case Attributes.CM_P256:\n              $bg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba;\n              break;\n            case Attributes.CM_RGB:\n              $bg = ((this.result.bg & Attributes.RGB_MASK) << 8) | 0xFF;\n              break;\n            // No need to consider default bg color here as it's not possible\n          }\n        }\n        // Blend with selection bg color\n        $bg = rgba.blend(\n          $bg,\n          ((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80\n        ) >> 8 & Attributes.RGB_MASK;\n      } else {\n        $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK;\n      }\n      $hasBg = true;\n\n      // Apply explicit selection foreground if present\n      if ($colors.selectionForeground) {\n        $fg = $colors.selectionForeground.rgba >> 8 & Attributes.RGB_MASK;\n        $hasFg = true;\n      }\n\n      // Overwrite fg as bg if it's a special decorative glyph (eg. powerline)\n      if (treatGlyphAsBackgroundColor(cell.getCode())) {\n        // Inverse default background should be treated as transparent\n        if (\n          (this.result.fg & FgFlags.INVERSE) &&\n          (this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT\n        ) {\n          $fg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK;\n        } else {\n\n          if (this.result.fg & FgFlags.INVERSE) {\n            switch (this.result.bg & Attributes.CM_MASK) {\n              case Attributes.CM_P16:\n              case Attributes.CM_P256:\n                $fg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba;\n                break;\n              case Attributes.CM_RGB:\n                $fg = ((this.result.bg & Attributes.RGB_MASK) << 8) | 0xFF;\n                break;\n              // No need to consider default bg color here as it's not possible\n            }\n          } else {\n            switch (this.result.fg & Attributes.CM_MASK) {\n              case Attributes.CM_P16:\n              case Attributes.CM_P256:\n                $fg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba;\n                break;\n              case Attributes.CM_RGB:\n                $fg = ((this.result.fg & Attributes.RGB_MASK) << 8) | 0xFF;\n                break;\n              case Attributes.CM_DEFAULT:\n              default:\n                $fg = this._themeService.colors.foreground.rgba;\n            }\n          }\n\n          $fg = rgba.blend(\n            $fg,\n            ((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80\n          ) >> 8 & Attributes.RGB_MASK;\n        }\n        $hasFg = true;\n      }\n    }\n\n    // Apply decorations on the top layer\n    this._decorationService.forEachDecorationAtCell(x, y, 'top', d => {\n      if (d.backgroundColorRGB) {\n        $bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;\n        $hasBg = true;\n      }\n      if (d.foregroundColorRGB) {\n        $fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK;\n        $hasFg = true;\n      }\n    });\n\n    // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag\n    // ahead of time in order to use the correct cache key\n    if ($hasBg) {\n      if ($isSelected) {\n        // Non-RGB attributes from model + force non-dim + override + force RGB color mode\n        $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB;\n      } else {\n        // Non-RGB attributes from model + override + force RGB color mode\n        $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB;\n      }\n    }\n    if ($hasFg) {\n      // Non-RGB attributes from model + force disable inverse + override + force RGB color mode\n      $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB;\n    }\n\n    // Handle case where inverse was specified by only one of bg override or fg override was set,\n    // resolving the other inverse color and setting the inverse flag if needed.\n    if (this.result.fg & FgFlags.INVERSE) {\n      if ($hasBg && !$hasFg) {\n        // Resolve bg color type (default color has a different meaning in fg vs bg)\n        if ((this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {\n          $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB;\n        } else {\n          $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this.result.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);\n        }\n        $hasFg = true;\n      }\n      if (!$hasBg && $hasFg) {\n        // Resolve bg color type (default color has a different meaning in fg vs bg)\n        if ((this.result.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {\n          $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB;\n        } else {\n          $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this.result.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);\n        }\n        $hasBg = true;\n      }\n    }\n\n    // Release object\n    $colors = undefined;\n\n    // Use the override if it exists\n    this.result.bg = $hasBg ? $bg : this.result.bg;\n    this.result.fg = $hasFg ? $fg : this.result.fg;\n\n    // Reset overrides variantOffset\n    this.result.ext &= ~ExtFlags.VARIANT_OFFSET;\n    this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET;\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/CharAtlasCache.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { TextureAtlas } from './TextureAtlas';\nimport { ITerminalOptions, Terminal } from '@xterm/xterm';\nimport { ITerminal, ReadonlyColorSet } from 'browser/Types';\nimport { ICharAtlasConfig, ITextureAtlas } from './Types';\nimport { generateConfig, configEquals } from './CharAtlasUtils';\nimport type { ILogService } from 'common/services/Services';\n\ninterface ITextureAtlasCacheEntry {\n  atlas: ITextureAtlas;\n  config: ICharAtlasConfig;\n  // N.B. This implementation potentially holds onto copies of the terminal forever, so\n  // this may cause memory leaks.\n  ownedBy: Terminal[];\n}\n\nconst charAtlasCache: ITextureAtlasCacheEntry[] = [];\n\n/**\n * Acquires a char atlas, either generating a new one or returning an existing\n * one that is in use by another terminal.\n */\nexport function acquireTextureAtlas(\n  terminal: Terminal,\n  options: Required<ITerminalOptions>,\n  colors: ReadonlyColorSet,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  deviceCharWidth: number,\n  deviceCharHeight: number,\n  devicePixelRatio: number,\n  deviceMaxTextureSize: number,\n  customGlyphs: boolean = true\n): ITextureAtlas {\n  const newConfig = generateConfig(deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, options, colors, devicePixelRatio, deviceMaxTextureSize, customGlyphs);\n\n  // Check to see if the terminal already owns this config\n  for (let i = 0; i < charAtlasCache.length; i++) {\n    const entry = charAtlasCache[i];\n    const ownedByIndex = entry.ownedBy.indexOf(terminal);\n    if (ownedByIndex >= 0) {\n      if (configEquals(entry.config, newConfig)) {\n        return entry.atlas;\n      }\n      // The configs differ, release the terminal from the entry\n      if (entry.ownedBy.length === 1) {\n        entry.atlas.dispose();\n        charAtlasCache.splice(i, 1);\n      } else {\n        entry.ownedBy.splice(ownedByIndex, 1);\n      }\n      break;\n    }\n  }\n\n  // Try match a char atlas from the cache\n  for (let i = 0; i < charAtlasCache.length; i++) {\n    const entry = charAtlasCache[i];\n    if (configEquals(entry.config, newConfig)) {\n      // Add the terminal to the cache entry and return\n      entry.ownedBy.push(terminal);\n      return entry.atlas;\n    }\n  }\n\n  const core: ITerminal = (terminal as any)._core;\n  const logService = (core as any)._logService as ILogService;\n  const newEntry: ITextureAtlasCacheEntry = {\n    atlas: new TextureAtlas(document, newConfig, core.unicodeService, logService),\n    config: newConfig,\n    ownedBy: [terminal]\n  };\n  charAtlasCache.push(newEntry);\n  return newEntry.atlas;\n}\n\n/**\n * Removes a terminal reference from the cache, allowing its memory to be freed.\n * @param terminal The terminal to remove.\n */\nexport function removeTerminalFromCache(terminal: Terminal): void {\n  for (let i = 0; i < charAtlasCache.length; i++) {\n    const index = charAtlasCache[i].ownedBy.indexOf(terminal);\n    if (index !== -1) {\n      if (charAtlasCache[i].ownedBy.length === 1) {\n        // Remove the cache entry if it's the only terminal\n        charAtlasCache[i].atlas.dispose();\n        charAtlasCache.splice(i, 1);\n      } else {\n        // Remove the reference from the cache entry\n        charAtlasCache[i].ownedBy.splice(index, 1);\n      }\n      break;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/CharAtlasUtils.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharAtlasConfig } from './Types';\nimport { Attributes } from 'common/buffer/Constants';\nimport { ITerminalOptions } from '@xterm/xterm';\nimport { IColorSet, ReadonlyColorSet } from 'browser/Types';\nimport { NULL_COLOR } from 'common/Color';\n\nexport function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {\n  // null out some fields that don't matter\n  const clonedColors: IColorSet = {\n    foreground: colors.foreground,\n    background: colors.background,\n    cursor: NULL_COLOR,\n    cursorAccent: NULL_COLOR,\n    selectionForeground: NULL_COLOR,\n    selectionBackgroundTransparent: NULL_COLOR,\n    selectionBackgroundOpaque: NULL_COLOR,\n    selectionInactiveBackgroundTransparent: NULL_COLOR,\n    selectionInactiveBackgroundOpaque: NULL_COLOR,\n    overviewRulerBorder: NULL_COLOR,\n    scrollbarSliderBackground: NULL_COLOR,\n    scrollbarSliderHoverBackground: NULL_COLOR,\n    scrollbarSliderActiveBackground: NULL_COLOR,\n    // For the static char atlas, we only use the first 16 colors, but we need all 256 for the\n    // dynamic character atlas.\n    ansi: colors.ansi.slice(),\n    contrastCache: colors.contrastCache,\n    halfContrastCache: colors.halfContrastCache\n  };\n  return {\n    customGlyphs,\n    devicePixelRatio,\n    deviceMaxTextureSize,\n    letterSpacing: options.letterSpacing,\n    lineHeight: options.lineHeight,\n    deviceCellWidth: deviceCellWidth,\n    deviceCellHeight: deviceCellHeight,\n    deviceCharWidth: deviceCharWidth,\n    deviceCharHeight: deviceCharHeight,\n    fontFamily: options.fontFamily,\n    fontSize: options.fontSize,\n    fontWeight: options.fontWeight,\n    fontWeightBold: options.fontWeightBold,\n    allowTransparency: options.allowTransparency,\n    drawBoldTextInBrightColors: options.drawBoldTextInBrightColors,\n    minimumContrastRatio: options.minimumContrastRatio,\n    colors: clonedColors\n  };\n}\n\nexport function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {\n  for (let i = 0; i < a.colors.ansi.length; i++) {\n    if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) {\n      return false;\n    }\n  }\n  return a.devicePixelRatio === b.devicePixelRatio &&\n      a.customGlyphs === b.customGlyphs &&\n      a.lineHeight === b.lineHeight &&\n      a.letterSpacing === b.letterSpacing &&\n      a.fontFamily === b.fontFamily &&\n      a.fontSize === b.fontSize &&\n      a.fontWeight === b.fontWeight &&\n      a.fontWeightBold === b.fontWeightBold &&\n      a.allowTransparency === b.allowTransparency &&\n      a.deviceCharWidth === b.deviceCharWidth &&\n      a.deviceCharHeight === b.deviceCharHeight &&\n      a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&\n      a.minimumContrastRatio === b.minimumContrastRatio &&\n      a.colors.foreground.rgba === b.colors.foreground.rgba &&\n      a.colors.background.rgba === b.colors.background.rgba;\n}\n\nexport function is256Color(colorCode: number): boolean {\n  return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256;\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/Constants.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { isFirefox, isLegacyEdge } from 'common/Platform';\n\nexport const DIM_OPACITY = 0.5;\n// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge\n// would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly\n// unaligned Powerline fonts (PR 3356#issuecomment-850928179).\nexport const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';\n"
  },
  {
    "path": "addons/addon-webgl/src/CursorBlinkStateManager.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { RendererConstants } from 'browser/renderer/shared/Constants';\nimport { ICoreBrowserService } from 'browser/services/Services';\n\nconst enum Constants {\n  /**\n   * The time between cursor blinks.\n   */\n  BLINK_INTERVAL = 600,\n}\n\nexport class CursorBlinkStateManager {\n  public isCursorVisible: boolean;\n\n  private _animationFrame: number | undefined;\n  private _blinkStartTimeout: number | undefined;\n  private _blinkInterval: number | undefined;\n  private _idleTimeout: number | undefined;\n  private _isIdlePaused: boolean = false;\n\n  /**\n   * The time at which the animation frame was restarted, this is used on the\n   * next render to restart the timers so they don't need to restart the timers\n   * multiple times over a short period.\n   */\n  private _animationTimeRestarted: number | undefined;\n\n  constructor(\n    private _renderCallback: () => void,\n    private _coreBrowserService: ICoreBrowserService\n  ) {\n    this.isCursorVisible = true;\n    if (this._coreBrowserService.isFocused) {\n      this._restartInterval();\n      this._resetIdleTimer();\n    }\n  }\n\n  public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }\n\n  public dispose(): void {\n    if (this._blinkInterval) {\n      this._coreBrowserService.window.clearInterval(this._blinkInterval);\n      this._blinkInterval = undefined;\n    }\n    if (this._blinkStartTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);\n      this._blinkStartTimeout = undefined;\n    }\n    if (this._animationFrame) {\n      this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);\n      this._animationFrame = undefined;\n    }\n    if (this._idleTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._idleTimeout);\n      this._idleTimeout = undefined;\n    }\n  }\n\n  public restartBlinkAnimation(): void {\n    if (this._isIdlePaused) {\n      this._resetIdleTimer();\n    }\n    if (this.isPaused) {\n      return;\n    }\n    // Save a timestamp so that the restart can be done on the next interval\n    this._animationTimeRestarted = Date.now();\n    // Force a cursor render to ensure it's visible and in the correct position\n    this.isCursorVisible = true;\n    if (!this._animationFrame) {\n      this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {\n        this._renderCallback();\n        this._animationFrame = undefined;\n      });\n    }\n  }\n\n  private _restartInterval(timeToStart: number = Constants.BLINK_INTERVAL): void {\n    // Clear any existing interval\n    if (this._blinkInterval) {\n      this._coreBrowserService.window.clearInterval(this._blinkInterval);\n      this._blinkInterval = undefined;\n    }\n\n    // Setup the initial timeout which will hide the cursor, this is done before\n    // the regular interval is setup in order to support restarting the blink\n    // animation in a lightweight way (without thrashing clearInterval and\n    // setInterval).\n    this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => {\n      // Check if another animation restart was requested while this was being\n      // started\n      if (this._animationTimeRestarted) {\n        const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);\n        this._animationTimeRestarted = undefined;\n        if (time > 0) {\n          this._restartInterval(time);\n          return;\n        }\n      }\n\n      // Hide the cursor\n      this.isCursorVisible = false;\n      this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {\n        this._renderCallback();\n        this._animationFrame = undefined;\n      });\n\n      // Setup the blink interval\n      this._blinkInterval = this._coreBrowserService.window.setInterval(() => {\n        // Adjust the animation time if it was restarted\n        if (this._animationTimeRestarted) {\n          // calc time diff\n          // Make restart interval do a setTimeout initially?\n          const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);\n          this._animationTimeRestarted = undefined;\n          this._restartInterval(time);\n          return;\n        }\n\n        // Invert visibility and render\n        this.isCursorVisible = !this.isCursorVisible;\n        this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {\n          this._renderCallback();\n          this._animationFrame = undefined;\n        });\n      }, Constants.BLINK_INTERVAL);\n    }, timeToStart);\n  }\n\n  public pause(): void {\n    this.isCursorVisible = true;\n    this._isIdlePaused = false;\n    if (this._blinkInterval) {\n      this._coreBrowserService.window.clearInterval(this._blinkInterval);\n      this._blinkInterval = undefined;\n    }\n    if (this._blinkStartTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);\n      this._blinkStartTimeout = undefined;\n    }\n    if (this._animationFrame) {\n      this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);\n      this._animationFrame = undefined;\n    }\n    if (this._idleTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._idleTimeout);\n      this._idleTimeout = undefined;\n    }\n  }\n\n  public resume(): void {\n    // Clear out any existing timers just in case\n    this.pause();\n\n    this._animationTimeRestarted = undefined;\n    this._restartInterval();\n    this._resetIdleTimer();\n    this.restartBlinkAnimation();\n  }\n\n  /**\n   * Resets the idle timer. If the terminal is idle for the idle timeout period,\n   * the cursor blinking will stop.\n   */\n  private _resetIdleTimer(): void {\n    this._isIdlePaused = false;\n    if (this._idleTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._idleTimeout);\n    }\n    this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {\n      this._stopBlinkingDueToIdle();\n    }, RendererConstants.CURSOR_BLINK_IDLE_TIMEOUT);\n  }\n\n  /**\n   * Stops cursor blinking due to idle timeout.\n   */\n  private _stopBlinkingDueToIdle(): void {\n    // Make cursor visible and stop blinking\n    this.isCursorVisible = true;\n    this._isIdlePaused = true;\n    if (this._blinkInterval) {\n      this._coreBrowserService.window.clearInterval(this._blinkInterval);\n      this._blinkInterval = undefined;\n    }\n    if (this._blinkStartTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);\n      this._blinkStartTimeout = undefined;\n    }\n    if (this._animationFrame) {\n      this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);\n      this._animationFrame = undefined;\n    }\n    // Clear the idle timeout as we've already acted on it\n    this._coreBrowserService.window.clearTimeout(this._idleTimeout);\n    this._idleTimeout = undefined;\n    // Trigger a render to show the cursor in its final visible state\n    this._renderCallback();\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/DevicePixelObserver.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { toDisposable, IDisposable } from 'common/Lifecycle';\n\nexport function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {\n  // Observe any resizes to the element and extract the actual pixel size of the element if the\n  // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when\n  // converting between CSS pixels and device pixels which causes blurry rendering when device\n  // pixel ratio is not a round number.\n  let observer: ResizeObserver | undefined = new parentWindow.ResizeObserver((entries) => {\n    const entry = entries.find((entry) => entry.target === element);\n    if (!entry) {\n      return;\n    }\n\n    // Disconnect if devicePixelContentBoxSize isn't supported by the browser\n    if (!('devicePixelContentBoxSize' in entry)) {\n      observer?.disconnect();\n      observer = undefined;\n      return;\n    }\n\n    // Fire the callback, ignore events where the dimensions are 0x0 as the canvas is likely hidden\n    const width = entry.devicePixelContentBoxSize[0].inlineSize;\n    const height = entry.devicePixelContentBoxSize[0].blockSize;\n    if (width > 0 && height > 0) {\n      callback(width, height);\n    }\n  });\n  try {\n    observer.observe(element, { box: ['device-pixel-content-box'] } as any);\n  } catch {\n    observer.disconnect();\n    observer = undefined;\n  }\n  return toDisposable(() => observer?.disconnect());\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/GlyphRenderer.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { TextureAtlas } from './TextureAtlas';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport { NULL_CELL_CODE } from 'common/buffer/Constants';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { Terminal } from '@xterm/xterm';\nimport { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';\nimport { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';\nimport type { IOptionsService } from 'common/services/Services';\nimport { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\n\ninterface IVertices {\n  attributes: Float32Array;\n  /**\n   * These buffers are the ones used to bind to WebGL, the reason there are\n   * multiple is to allow double buffering to work as you cannot modify the\n   * buffer while it's being used by the GPU. Having multiple lets us start\n   * working on the next frame.\n   */\n  attributesBuffers: Float32Array[];\n  count: number;\n}\n\nconst enum VertexAttribLocations {\n  UNIT_QUAD = 0,\n  CELL_POSITION = 1,\n  OFFSET = 2,\n  SIZE = 3,\n  TEXPAGE = 4,\n  TEXCOORD = 5,\n  TEXSIZE = 6\n}\n\nconst vertexShaderSource = `#version 300 es\nlayout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;\nlayout (location = ${VertexAttribLocations.CELL_POSITION}) in vec2 a_cellpos;\nlayout (location = ${VertexAttribLocations.OFFSET}) in vec2 a_offset;\nlayout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;\nlayout (location = ${VertexAttribLocations.TEXPAGE}) in float a_texpage;\nlayout (location = ${VertexAttribLocations.TEXCOORD}) in vec2 a_texcoord;\nlayout (location = ${VertexAttribLocations.TEXSIZE}) in vec2 a_texsize;\n\nuniform mat4 u_projection;\nuniform vec2 u_resolution;\n\nout vec2 v_texcoord;\nflat out int v_texpage;\n\nvoid main() {\n  vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);\n  gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n  v_texpage = int(a_texpage);\n  v_texcoord = a_texcoord + a_unitquad * a_texsize;\n}`;\n\nfunction createFragmentShaderSource(maxFragmentShaderTextureUnits: number): string {\n  let textureConditionals = '';\n  for (let i = 1; i < maxFragmentShaderTextureUnits; i++) {\n    textureConditionals += ` else if (v_texpage == ${i}) { outColor = texture(u_texture[${i}], v_texcoord); }`;\n  }\n  return (`#version 300 es\nprecision lowp float;\n\nin vec2 v_texcoord;\nflat in int v_texpage;\n\nuniform sampler2D u_texture[${maxFragmentShaderTextureUnits}];\n\nout vec4 outColor;\n\nvoid main() {\n  if (v_texpage == 0) {\n    outColor = texture(u_texture[0], v_texcoord);\n  } ${textureConditionals}\n}`);\n}\n\nconst INDICES_PER_CELL = 11;\nconst BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT;\nconst CELL_POSITION_INDICES = 2;\n\n// Work variables to avoid garbage collection\nlet $i = 0;\nlet $glyph: IRasterizedGlyph | undefined = undefined;\nlet $leftCellPadding = 0;\nlet $clippedPixels = 0;\n\nexport class GlyphRenderer extends Disposable {\n  private readonly _program: WebGLProgram;\n  private readonly _vertexArrayObject: IWebGLVertexArrayObject;\n  private readonly _projectionLocation: WebGLUniformLocation;\n  private readonly _resolutionLocation: WebGLUniformLocation;\n  private readonly _textureLocation: WebGLUniformLocation;\n  private readonly _atlasTextures: GLTexture[];\n  private readonly _attributesBuffer: WebGLBuffer;\n\n  private _atlas: ITextureAtlas | undefined;\n  private _activeBuffer: number = 0;\n  private readonly _vertices: IVertices = {\n    count: 0,\n    attributes: new Float32Array(0),\n    attributesBuffers: [\n      new Float32Array(0),\n      new Float32Array(0)\n    ]\n  };\n\n  constructor(\n    private readonly _terminal: Terminal,\n    private readonly _gl: IWebGL2RenderingContext,\n    private _dimensions: IRenderDimensions,\n    private readonly _optionsService: IOptionsService\n  ) {\n    super();\n\n    const gl = this._gl;\n\n    if (TextureAtlas.maxAtlasPages === undefined) {\n      // Typically 8 or 16\n      TextureAtlas.maxAtlasPages = Math.min(32, throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number | null));\n      // Almost all clients will support >= 4096\n      TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);\n    }\n\n    this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages)));\n    this._register(toDisposable(() => gl.deleteProgram(this._program)));\n\n    // Uniform locations\n    this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));\n    this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));\n    this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));\n\n    // Create and set the vertex array object\n    this._vertexArrayObject = gl.createVertexArray();\n    gl.bindVertexArray(this._vertexArrayObject);\n\n    // Setup a_unitquad, this defines the 4 vertices of a rectangle\n    const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);\n    const unitQuadVerticesBuffer = gl.createBuffer();\n    this._register(toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer)));\n    gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer);\n    gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW);\n    gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);\n    gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);\n\n    // Setup the unit quad element array buffer, this points to indices in\n    // unitQuadVertices to allow is to draw 2 triangles from the vertices via a\n    // triangle strip\n    const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);\n    const elementIndicesBuffer = gl.createBuffer();\n    this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer);\n    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);\n\n    // Setup attributes\n    this._attributesBuffer = throwIfFalsy(gl.createBuffer());\n    this._register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer)));\n    gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);\n    gl.enableVertexAttribArray(VertexAttribLocations.OFFSET);\n    gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0);\n    gl.vertexAttribDivisor(VertexAttribLocations.OFFSET, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.SIZE);\n    gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.TEXPAGE);\n    gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.TEXPAGE, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD);\n    gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE);\n    gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION);\n    gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1);\n\n    // Setup static uniforms\n    gl.useProgram(this._program);\n    const textureUnits = new Int32Array(TextureAtlas.maxAtlasPages);\n    for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) {\n      textureUnits[i] = i;\n    }\n    gl.uniform1iv(this._textureLocation, textureUnits);\n    gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);\n\n    // Setup 1x1 red pixel textures for all potential atlas pages, if one of these invalid textures\n    // is ever drawn it will show characters as red rectangles.\n    this._atlasTextures = [];\n    for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) {\n      const glTexture = new GLTexture(throwIfFalsy(gl.createTexture()));\n      this._register(toDisposable(() => gl.deleteTexture(glTexture.texture)));\n      gl.activeTexture(gl.TEXTURE0 + i);\n      gl.bindTexture(gl.TEXTURE_2D, glTexture.texture);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));\n      this._atlasTextures[i] = glTexture;\n    }\n\n    // Allow drawing of transparent texture\n    gl.enable(gl.BLEND);\n    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n    // Set viewport\n    this.handleResize();\n  }\n\n  public beginFrame(): boolean {\n    return this._atlas ? this._atlas.beginFrame() : true;\n  }\n\n  public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {\n    // Since this function is called for every cell (`rows*cols`), it must be very optimized. It\n    // should not instantiate any variables unless a new glyph is drawn to the cache where the\n    // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for\n    // each glyph.\n    this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg);\n  }\n\n  private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {\n    $i = (y * this._terminal.cols + x) * INDICES_PER_CELL;\n\n    // Exit early if this is a null character, allow space character to continue as it may have\n    // underline/strikethrough styles\n    if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {\n      array.fill(0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);\n      return;\n    }\n\n    if (!this._atlas) {\n      return;\n    }\n\n    // Get the glyph\n    if (chars && chars.length > 1) {\n      $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false, this._terminal.element);\n    } else {\n      $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false, this._terminal.element);\n    }\n\n    $leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2);\n    if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) {\n      $clippedPixels = $glyph.offset.x - $leftCellPadding;\n      // a_origin\n      array[$i    ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.device.char.left;\n      array[$i + 1] = -$glyph.offset.y + this._dimensions.device.char.top;\n      // a_size\n      array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.device.canvas.width;\n      array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height;\n      // a_texpage\n      array[$i + 4] = $glyph.texturePage;\n      // a_texcoord\n      array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.pages[$glyph.texturePage].canvas.width;\n      array[$i + 6] = $glyph.texturePositionClipSpace.y;\n      // a_texsize\n      array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.pages[$glyph.texturePage].canvas.width;\n      array[$i + 8] = $glyph.sizeClipSpace.y;\n    } else {\n      // a_origin\n      array[$i    ] = -$glyph.offset.x + this._dimensions.device.char.left;\n      array[$i + 1] = -$glyph.offset.y + this._dimensions.device.char.top;\n      // a_size\n      array[$i + 2] = $glyph.size.x / this._dimensions.device.canvas.width;\n      array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height;\n      // a_texpage\n      array[$i + 4] = $glyph.texturePage;\n      // a_texcoord\n      array[$i + 5] = $glyph.texturePositionClipSpace.x;\n      array[$i + 6] = $glyph.texturePositionClipSpace.y;\n      // a_texsize\n      array[$i + 7] = $glyph.sizeClipSpace.x;\n      array[$i + 8] = $glyph.sizeClipSpace.y;\n    }\n    // a_cellpos only changes on resize\n\n    // Reduce scale horizontally for wide glyphs printed in cells that would overlap with the\n    // following cell (ie. the width is not 2).\n    if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) {\n      if (allowRescaling(code, width, $glyph.size.x, this._dimensions.device.cell.width)) {\n        array[$i + 2] = (this._dimensions.device.cell.width - 1) / this._dimensions.device.canvas.width; // - 1 to improve readability\n      }\n    }\n  }\n\n  public clear(): void {\n    const terminal = this._terminal;\n    const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;\n\n    // Clear vertices\n    if (this._vertices.count !== newCount) {\n      this._vertices.attributes = new Float32Array(newCount);\n    } else {\n      this._vertices.attributes.fill(0);\n    }\n    let i = 0;\n    for (; i < this._vertices.attributesBuffers.length; i++) {\n      if (this._vertices.count !== newCount) {\n        this._vertices.attributesBuffers[i] = new Float32Array(newCount);\n      } else {\n        this._vertices.attributesBuffers[i].fill(0);\n      }\n    }\n    this._vertices.count = newCount;\n    i = 0;\n    for (let y = 0; y < terminal.rows; y++) {\n      for (let x = 0; x < terminal.cols; x++) {\n        this._vertices.attributes[i + 9] = x / terminal.cols;\n        this._vertices.attributes[i + 10] = y / terminal.rows;\n        i += INDICES_PER_CELL;\n      }\n    }\n  }\n\n  public handleResize(): void {\n    const gl = this._gl;\n    gl.useProgram(this._program);\n    gl.viewport(0, 0, this._dimensions.device.canvas.width, this._dimensions.device.canvas.height);\n    gl.uniform2f(this._resolutionLocation, this._dimensions.device.canvas.width, this._dimensions.device.canvas.height);\n    this.clear();\n  }\n\n  public render(renderModel: IRenderModel): void {\n    if (!this._atlas) {\n      return;\n    }\n\n    const gl = this._gl;\n\n    gl.useProgram(this._program);\n    gl.bindVertexArray(this._vertexArrayObject);\n\n    // Alternate buffers each frame as the active buffer gets locked while it's in use by the GPU\n    this._activeBuffer = (this._activeBuffer + 1) % 2;\n    const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer];\n\n    // Copy data for each cell of each line up to its line length (the last non-whitespace cell)\n    // from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.\n    // The reasons for this are as follows:\n    // - So the active buffer can be alternated so we don't get blocked on rendering finishing\n    // - To copy either the normal attributes buffer or the selection attributes buffer when there\n    //   is a selection\n    // - So we don't send vertices for all the line-ending whitespace to the GPU\n    let bufferLength = 0;\n    for (let y = 0; y < renderModel.lineLengths.length; y++) {\n      const si = y * this._terminal.cols * INDICES_PER_CELL;\n      const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);\n      activeBuffer.set(sub, bufferLength);\n      bufferLength += sub.length;\n    }\n\n    // Bind the attributes buffer\n    gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);\n    gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);\n\n    // Bind the atlas page texture if they have changed\n    for (let i = 0; i < this._atlas.pages.length; i++) {\n      if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {\n        this._bindAtlasPageTexture(gl, this._atlas, i);\n      }\n    }\n\n    // Draw the viewport\n    gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL);\n  }\n\n  public setAtlas(atlas: ITextureAtlas): void {\n    this._atlas = atlas;\n    for (const glTexture of this._atlasTextures) {\n      glTexture.version = -1;\n    }\n  }\n\n  private _bindAtlasPageTexture(gl: IWebGL2RenderingContext, atlas: ITextureAtlas, i: number): void {\n    gl.activeTexture(gl.TEXTURE0 + i);\n    gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas);\n    gl.generateMipmap(gl.TEXTURE_2D);\n    this._atlasTextures[i].version = atlas.pages[i].version;\n  }\n\n  public setDimensions(dimensions: IRenderDimensions): void {\n    this._dimensions = dimensions;\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/RectangleRenderer.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport { IThemeService } from 'browser/services/Services';\nimport { ReadonlyColorSet } from 'browser/Types';\nimport { Attributes, FgFlags } from 'common/buffer/Constants';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IColor } from 'common/Types';\nimport { Terminal } from '@xterm/xterm';\nimport { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';\nimport { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';\nimport { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';\nimport { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\n\nconst enum VertexAttribLocations {\n  POSITION = 0,\n  SIZE = 1,\n  COLOR = 2,\n  UNIT_QUAD = 3\n}\n\nconst vertexShaderSource = `#version 300 es\nlayout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position;\nlayout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;\nlayout (location = ${VertexAttribLocations.COLOR}) in vec4 a_color;\nlayout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;\n\nuniform mat4 u_projection;\n\nout vec4 v_color;\n\nvoid main() {\n  vec2 zeroToOne = a_position + (a_unitquad * a_size);\n  gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n  v_color = a_color;\n}`;\n\nconst fragmentShaderSource = `#version 300 es\nprecision lowp float;\n\nin vec4 v_color;\n\nout vec4 outColor;\n\nvoid main() {\n  outColor = v_color;\n}`;\n\nconst INDICES_PER_RECTANGLE = 8;\nconst BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEMENT;\n\nconst INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE;\n\nclass Vertices {\n  public attributes: Float32Array;\n  public count: number;\n\n  constructor() {\n    this.attributes = new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY);\n    this.count = 0;\n  }\n}\n\n// Work variables to avoid garbage collection\nlet $rgba = 0;\nlet $x1 = 0;\nlet $y1 = 0;\nlet $r = 0;\nlet $g = 0;\nlet $b = 0;\nlet $a = 0;\n\nexport class RectangleRenderer extends Disposable {\n\n  private _program: WebGLProgram;\n  private _vertexArrayObject: IWebGLVertexArrayObject;\n  private _attributesBuffer: WebGLBuffer;\n  private _projectionLocation: WebGLUniformLocation;\n  private _bgFloat!: Float32Array;\n  private _cursorFloat!: Float32Array;\n\n  private _vertices: Vertices = new Vertices();\n  private _verticesCursor: Vertices = new Vertices();\n\n  constructor(\n    private _terminal: Terminal,\n    private _gl: IWebGL2RenderingContext,\n    private _dimensions: IRenderDimensions,\n    private readonly _themeService: IThemeService\n  ) {\n    super();\n\n    const gl = this._gl;\n\n    this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));\n    this._register(toDisposable(() => gl.deleteProgram(this._program)));\n\n    // Uniform locations\n    this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));\n\n    // Create and set the vertex array object\n    this._vertexArrayObject = gl.createVertexArray();\n    gl.bindVertexArray(this._vertexArrayObject);\n\n    // Setup a_unitquad, this defines the 4 vertices of a rectangle\n    const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);\n    const unitQuadVerticesBuffer = gl.createBuffer();\n    this._register(toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer)));\n    gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer);\n    gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW);\n    gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);\n    gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);\n\n    // Setup the unit quad element array buffer, this points to indices in\n    // unitQuadVertices to allow is to draw 2 triangles from the vertices via a\n    // triangle strip\n    const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);\n    const elementIndicesBuffer = gl.createBuffer();\n    this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer);\n    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);\n\n    // Setup attributes\n    this._attributesBuffer = throwIfFalsy(gl.createBuffer());\n    this._register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer)));\n    gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);\n    gl.enableVertexAttribArray(VertexAttribLocations.POSITION);\n    gl.vertexAttribPointer(VertexAttribLocations.POSITION, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 0);\n    gl.vertexAttribDivisor(VertexAttribLocations.POSITION, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.SIZE);\n    gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 2 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1);\n    gl.enableVertexAttribArray(VertexAttribLocations.COLOR);\n    gl.vertexAttribPointer(VertexAttribLocations.COLOR, 4, gl.FLOAT, false, BYTES_PER_RECTANGLE, 4 * Float32Array.BYTES_PER_ELEMENT);\n    gl.vertexAttribDivisor(VertexAttribLocations.COLOR, 1);\n\n    this._updateCachedColors(_themeService.colors);\n    this._register(this._themeService.onChangeColors(e => {\n      this._updateCachedColors(e);\n      this._updateViewportRectangle();\n    }));\n  }\n\n  public renderBackgrounds(): void {\n    this._renderVertices(this._vertices);\n  }\n\n  public renderCursor(): void {\n    this._renderVertices(this._verticesCursor);\n  }\n\n  private _renderVertices(vertices: Vertices): void {\n    const gl = this._gl;\n\n    gl.useProgram(this._program);\n\n    gl.bindVertexArray(this._vertexArrayObject);\n\n    gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);\n\n    // Bind attributes buffer and draw\n    gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);\n    gl.bufferData(gl.ARRAY_BUFFER, vertices.attributes, gl.DYNAMIC_DRAW);\n    gl.drawElementsInstanced(this._gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, vertices.count);\n  }\n\n  public handleResize(): void {\n    this._updateViewportRectangle();\n  }\n\n  public setDimensions(dimensions: IRenderDimensions): void {\n    this._dimensions = dimensions;\n  }\n\n  private _updateCachedColors(colors: ReadonlyColorSet): void {\n    this._bgFloat = this._colorToFloat32Array(colors.background);\n    this._cursorFloat = this._colorToFloat32Array(colors.cursor);\n  }\n\n  private _updateViewportRectangle(): void {\n    // Set first rectangle that clears the screen\n    this._addRectangleFloat(\n      this._vertices.attributes,\n      0,\n      0,\n      0,\n      this._terminal.cols * this._dimensions.device.cell.width,\n      this._terminal.rows * this._dimensions.device.cell.height,\n      this._bgFloat\n    );\n  }\n\n  public updateBackgrounds(model: IRenderModel): void {\n    const terminal = this._terminal;\n    const vertices = this._vertices;\n\n    // Declare variable ahead of time to avoid garbage collection\n    let rectangleCount = 1;\n    let y: number;\n    let x: number;\n    let currentStartX: number;\n    let currentBg: number;\n    let currentFg: number;\n    let currentInverse: boolean;\n    let modelIndex: number;\n    let bg: number;\n    let fg: number;\n    let inverse: boolean;\n    let offset: number;\n\n    for (y = 0; y < terminal.rows; y++) {\n      currentStartX = -1;\n      currentBg = 0;\n      currentFg = 0;\n      currentInverse = false;\n      for (x = 0; x < terminal.cols; x++) {\n        modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;\n        bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET];\n        fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET];\n        inverse = !!(fg & FgFlags.INVERSE);\n        if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) {\n          // A rectangle needs to be drawn if going from non-default to another color\n          if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {\n            offset = rectangleCount++ * INDICES_PER_RECTANGLE;\n            this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y);\n          }\n          currentStartX = x;\n          currentBg = bg;\n          currentFg = fg;\n          currentInverse = inverse;\n        }\n      }\n      // Finish rectangle if it's still going\n      if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {\n        offset = rectangleCount++ * INDICES_PER_RECTANGLE;\n        this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y);\n      }\n    }\n    vertices.count = rectangleCount;\n  }\n\n  public updateCursor(model: IRenderModel): void {\n    const vertices = this._verticesCursor;\n    const cursor = model.cursor;\n    if (!cursor || cursor.style === 'block') {\n      vertices.count = 0;\n      return;\n    }\n\n    let offset: number;\n    let rectangleCount = 0;\n\n    if (cursor.style === 'bar' || cursor.style === 'outline') {\n      // Left edge\n      offset = rectangleCount++ * INDICES_PER_RECTANGLE;\n      this._addRectangleFloat(\n        vertices.attributes,\n        offset,\n        cursor.x * this._dimensions.device.cell.width,\n        cursor.y * this._dimensions.device.cell.height,\n        cursor.style === 'bar' ? cursor.dpr * cursor.cursorWidth : cursor.dpr,\n        this._dimensions.device.cell.height,\n        this._cursorFloat\n      );\n    }\n    if (cursor.style === 'underline' || cursor.style === 'outline') {\n      // Bottom edge\n      offset = rectangleCount++ * INDICES_PER_RECTANGLE;\n      this._addRectangleFloat(\n        vertices.attributes,\n        offset,\n        cursor.x * this._dimensions.device.cell.width,\n        (cursor.y + 1) * this._dimensions.device.cell.height - cursor.dpr,\n        cursor.width * this._dimensions.device.cell.width,\n        cursor.dpr,\n        this._cursorFloat\n      );\n    }\n    if (cursor.style === 'outline') {\n      // Top edge\n      offset = rectangleCount++ * INDICES_PER_RECTANGLE;\n      this._addRectangleFloat(\n        vertices.attributes,\n        offset,\n        cursor.x * this._dimensions.device.cell.width,\n        cursor.y * this._dimensions.device.cell.height,\n        cursor.width * this._dimensions.device.cell.width,\n        cursor.dpr,\n        this._cursorFloat\n      );\n      // Right edge\n      offset = rectangleCount++ * INDICES_PER_RECTANGLE;\n      this._addRectangleFloat(\n        vertices.attributes,\n        offset,\n        (cursor.x + cursor.width) * this._dimensions.device.cell.width - cursor.dpr,\n        cursor.y * this._dimensions.device.cell.height,\n        cursor.dpr,\n        this._dimensions.device.cell.height,\n        this._cursorFloat\n      );\n    }\n\n    vertices.count = rectangleCount;\n  }\n\n  private _updateRectangle(vertices: Vertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {\n    if (fg & FgFlags.INVERSE) {\n      switch (fg & Attributes.CM_MASK) {\n        case Attributes.CM_P16:\n        case Attributes.CM_P256:\n          $rgba = this._themeService.colors.ansi[fg & Attributes.PCOLOR_MASK].rgba;\n          break;\n        case Attributes.CM_RGB:\n          $rgba = (fg & Attributes.RGB_MASK) << 8;\n          break;\n        case Attributes.CM_DEFAULT:\n        default:\n          $rgba = this._themeService.colors.foreground.rgba;\n      }\n    } else {\n      switch (bg & Attributes.CM_MASK) {\n        case Attributes.CM_P16:\n        case Attributes.CM_P256:\n          $rgba = this._themeService.colors.ansi[bg & Attributes.PCOLOR_MASK].rgba;\n          break;\n        case Attributes.CM_RGB:\n          $rgba = (bg & Attributes.RGB_MASK) << 8;\n          break;\n        case Attributes.CM_DEFAULT:\n        default:\n          $rgba = this._themeService.colors.background.rgba;\n      }\n    }\n\n    if (vertices.attributes.length < offset + 4) {\n      vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);\n    }\n    $x1 = startX * this._dimensions.device.cell.width;\n    $y1 = y * this._dimensions.device.cell.height;\n    $r = (($rgba >> 24) & 0xFF) / 255;\n    $g = (($rgba >> 16) & 0xFF) / 255;\n    $b = (($rgba >> 8 ) & 0xFF) / 255;\n    $a = 1;\n\n    this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.device.cell.width, this._dimensions.device.cell.height, $r, $g, $b, $a);\n  }\n\n  private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void {\n    array[offset    ] = x1 / this._dimensions.device.canvas.width;\n    array[offset + 1] = y1 / this._dimensions.device.canvas.height;\n    array[offset + 2] = width / this._dimensions.device.canvas.width;\n    array[offset + 3] = height / this._dimensions.device.canvas.height;\n    array[offset + 4] = r;\n    array[offset + 5] = g;\n    array[offset + 6] = b;\n    array[offset + 7] = a;\n  }\n\n  private _addRectangleFloat(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, color: Float32Array): void {\n    array[offset    ] = x1 / this._dimensions.device.canvas.width;\n    array[offset + 1] = y1 / this._dimensions.device.canvas.height;\n    array[offset + 2] = width / this._dimensions.device.canvas.width;\n    array[offset + 3] = height / this._dimensions.device.canvas.height;\n    array[offset + 4] = color[0];\n    array[offset + 5] = color[1];\n    array[offset + 6] = color[2];\n    array[offset + 7] = color[3];\n  }\n\n  private _colorToFloat32Array(color: IColor): Float32Array {\n    return new Float32Array([\n      ((color.rgba >> 24) & 0xFF) / 255,\n      ((color.rgba >> 16) & 0xFF) / 255,\n      ((color.rgba >> 8 ) & 0xFF) / 255,\n      ((color.rgba      ) & 0xFF) / 255\n    ]);\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/RenderModel.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICursorRenderModel, IRenderModel } from './Types';\nimport { ISelectionRenderModel } from 'browser/renderer/shared/Types';\nimport { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';\n\nexport const RENDER_MODEL_INDICIES_PER_CELL = 4;\nexport const RENDER_MODEL_BG_OFFSET = 1;\nexport const RENDER_MODEL_FG_OFFSET = 2;\nexport const RENDER_MODEL_EXT_OFFSET = 3;\n\nexport const COMBINED_CHAR_BIT_MASK = 0x80000000;\n\nexport class RenderModel implements IRenderModel {\n  public cells: Uint32Array;\n  public lineLengths: Uint32Array;\n  public selection: ISelectionRenderModel;\n  public cursor?: ICursorRenderModel;\n\n  constructor() {\n    this.cells = new Uint32Array(0);\n    this.lineLengths = new Uint32Array(0);\n    this.selection = createSelectionRenderModel();\n  }\n\n  public resize(cols: number, rows: number): void {\n    const indexCount = cols * rows * RENDER_MODEL_INDICIES_PER_CELL;\n    if (indexCount !== this.cells.length) {\n      this.cells = new Uint32Array(indexCount);\n      this.lineLengths = new Uint32Array(rows);\n    }\n  }\n\n  public clear(): void {\n    this.cells.fill(0, 0);\n    this.lineLengths.fill(0, 0);\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/TextureAtlas.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorContrastCache } from 'browser/Types';\nimport { DIM_OPACITY, TEXT_BASELINE } from './Constants';\nimport { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';\nimport { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\nimport { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';\nimport { NULL_COLOR, channels, color, rgba } from 'common/Color';\nimport { FourKeyMap } from 'common/MultiKeyMap';\nimport { IdleTaskQueue } from 'common/TaskQueue';\nimport { IColor } from 'common/Types';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';\nimport { ILogService, IUnicodeService } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\n/**\n * A shared object which is used to draw nothing for a particular cell.\n */\nconst NULL_RASTERIZED_GLYPH: IRasterizedGlyph = {\n  texturePage: 0,\n  texturePosition: { x: 0, y: 0 },\n  texturePositionClipSpace: { x: 0, y: 0 },\n  offset: { x: 0, y: 0 },\n  size: { x: 0, y: 0 },\n  sizeClipSpace: { x: 0, y: 0 }\n};\n\nconst TMP_CANVAS_GLYPH_PADDING = 2;\n\nconst enum Constants {\n  /**\n   * The amount of pixel padding to allow in each row. Setting this to zero would make the atlas\n   * page pack as tightly as possible, but more pages would end up being created as a result.\n   */\n  ROW_PIXEL_THRESHOLD = 2,\n  /**\n   * The maximum texture size regardless of what the actual hardware maximum turns out to be. This\n   * is enforced to ensure uploading the texture still finishes in a reasonable amount of time. A\n   * 4096 squared image takes up 16MB of GPU memory.\n   */\n  FORCED_MAX_TEXTURE_SIZE = 4096\n}\n\ninterface ICharAtlasActiveRow {\n  x: number;\n  y: number;\n  height: number;\n}\n\n// Work variables to avoid garbage collection\nlet $glyph = undefined;\n\nexport class TextureAtlas implements ITextureAtlas {\n  private _didWarmUp: boolean = false;\n\n  private _cacheMap: FourKeyMap<number, number, number, number, IRasterizedGlyph> = new FourKeyMap();\n  private _cacheMapCombined: FourKeyMap<string, number, number, number, IRasterizedGlyph> = new FourKeyMap();\n\n  // The texture that the atlas is drawn to\n  private _pages: AtlasPage[] = [];\n  public get pages(): { canvas: HTMLCanvasElement, version: number }[] { return this._pages; }\n\n  // The set of atlas pages that can be written to\n  private _activePages: AtlasPage[] = [];\n  private _overflowSizePage: AtlasPage | undefined;\n\n  private _tmpCanvas: HTMLCanvasElement;\n  // A temporary context that glyphs are drawn to before being transfered to the atlas.\n  private _tmpCtx: CanvasRenderingContext2D;\n\n  private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };\n  private _workAttributeData: AttributeData = new AttributeData();\n\n  private _textureSize: number = 512;\n\n  public static maxAtlasPages: number | undefined;\n  public static maxTextureSize: number | undefined;\n\n  private readonly _onAddTextureAtlasCanvas = new Emitter<HTMLCanvasElement>();\n  public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;\n  private readonly _onRemoveTextureAtlasCanvas = new Emitter<HTMLCanvasElement>();\n  public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event;\n\n  constructor(\n    private readonly _document: Document,\n    private readonly _config: ICharAtlasConfig,\n    private readonly _unicodeService: IUnicodeService,\n    private readonly _logService: ILogService\n  ) {\n    this._createNewPage();\n    this._tmpCanvas = createCanvas(\n      _document,\n      this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2,\n      this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2\n    );\n    this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {\n      alpha: this._config.allowTransparency,\n      willReadFrequently: true\n    }));\n  }\n\n  public dispose(): void {\n    this._tmpCanvas.remove();\n    for (const page of this.pages) {\n      page.canvas.remove();\n    }\n    this._onAddTextureAtlasCanvas.dispose();\n  }\n\n  public warmUp(): void {\n    if (!this._didWarmUp) {\n      this._doWarmUp();\n      this._didWarmUp = true;\n    }\n  }\n\n  private _doWarmUp(): void {\n    // Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks\n    const queue = new IdleTaskQueue(this._logService);\n    for (let i = 33; i < 126; i++) {\n      queue.enqueue(() => {\n        if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {\n          const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, false, undefined);\n          this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph);\n        }\n      });\n    }\n  }\n\n  private _requestClearModel = false;\n  public beginFrame(): boolean {\n    return this._requestClearModel;\n  }\n\n  public clearTexture(): void {\n    if (this._pages[0].currentRow.x === 0 && this._pages[0].currentRow.y === 0) {\n      return;\n    }\n    for (const page of this._pages) {\n      page.clear();\n    }\n    this._cacheMap.clear();\n    this._cacheMapCombined.clear();\n    this._didWarmUp = false;\n  }\n\n  private _createNewPage(): AtlasPage {\n    // Try merge the set of the 4 most used pages of the largest size. This is is deferred to a\n    // microtask to ensure it does not interrupt textures that will be rendered in the current\n    // animation frame which would result in blank rendered areas. This is actually not that\n    // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.\n    if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) {\n      // Find the set of the largest 4 images, below the maximum size, with the highest\n      // percentages used\n      const pagesBySize = this._pages.filter(e => {\n        return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE);\n      }).sort((a, b) => {\n        if (b.canvas.width !== a.canvas.width) {\n          return b.canvas.width - a.canvas.width;\n        }\n        return b.percentageUsed - a.percentageUsed;\n      });\n      let sameSizeI = -1;\n      let size = 0;\n      for (let i = 0; i < pagesBySize.length; i++) {\n        if (pagesBySize[i].canvas.width !== size) {\n          sameSizeI = i;\n          size = pagesBySize[i].canvas.width;\n        } else if (i - sameSizeI === 3) {\n          break;\n        }\n      }\n\n      // Gather details of the merge\n      const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);\n\n      // Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot\n      // effectively reduce page count and merging would cause issues.\n      if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {\n        const newPage = new AtlasPage(this._document, this._textureSize);\n        this._pages.push(newPage);\n        this._activePages.push(newPage);\n        this._onAddTextureAtlasCanvas.fire(newPage.canvas);\n        return newPage;\n      }\n\n      const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1);\n      const mergedPageIndex = this.pages.length - mergingPages.length;\n\n      // Merge into the new page\n      const mergedPage = this._mergePages(mergingPages, mergedPageIndex);\n      mergedPage.version++;\n\n      // Delete the pages, shifting glyph texture pages as needed\n      for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {\n        this._deletePage(sortedMergingPagesIndexes[i]);\n      }\n\n      // Add the new merged page to the end\n      this.pages.push(mergedPage);\n\n      // Request the model to be cleared to refresh all texture pages.\n      this._requestClearModel = true;\n      this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);\n    }\n\n    // All new atlas pages are created small as they are highly dynamic\n    const newPage = new AtlasPage(this._document, this._textureSize);\n    this._pages.push(newPage);\n    this._activePages.push(newPage);\n    this._onAddTextureAtlasCanvas.fire(newPage.canvas);\n    return newPage;\n  }\n\n  private _mergePages(mergingPages: AtlasPage[], mergedPageIndex: number): AtlasPage {\n    const mergedSize = mergingPages[0].canvas.width * 2;\n    const mergedPage = new AtlasPage(this._document, mergedSize, mergingPages);\n    for (const [i, p] of mergingPages.entries()) {\n      const xOffset = i * p.canvas.width % mergedSize;\n      const yOffset = Math.floor(i / 2) * p.canvas.height;\n      mergedPage.ctx.drawImage(p.canvas, xOffset, yOffset);\n      for (const g of p.glyphs) {\n        g.texturePage = mergedPageIndex;\n        g.sizeClipSpace.x = g.size.x / mergedSize;\n        g.sizeClipSpace.y = g.size.y / mergedSize;\n        g.texturePosition.x += xOffset;\n        g.texturePosition.y += yOffset;\n        g.texturePositionClipSpace.x = g.texturePosition.x / mergedSize;\n        g.texturePositionClipSpace.y = g.texturePosition.y / mergedSize;\n      }\n\n      this._onRemoveTextureAtlasCanvas.fire(p.canvas);\n\n      // Remove the merging page from active pages if it was there\n      const index = this._activePages.indexOf(p);\n      if (index !== -1) {\n        this._activePages.splice(index, 1);\n      }\n    }\n    return mergedPage;\n  }\n\n  private _deletePage(pageIndex: number): void {\n    this._pages.splice(pageIndex, 1);\n    for (let j = pageIndex; j < this._pages.length; j++) {\n      const adjustingPage = this._pages[j];\n      for (const g of adjustingPage.glyphs) {\n        g.texturePage--;\n      }\n      adjustingPage.version++;\n    }\n  }\n\n  public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {\n    return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight, domContainer);\n  }\n\n  public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {\n    return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, restrictToCellHeight, domContainer);\n  }\n\n  /**\n   * Gets the glyphs texture coords, drawing the texture if it's not already\n   */\n  private _getFromCacheMap(\n    cacheMap: FourKeyMap<string | number, number, number, number, IRasterizedGlyph>,\n    key: string | number,\n    bg: number,\n    fg: number,\n    ext: number,\n    restrictToCellHeight: boolean,\n    domContainer: HTMLElement | undefined\n  ): IRasterizedGlyph {\n    $glyph = cacheMap.get(key, bg, fg, ext);\n    if (!$glyph) {\n      $glyph = this._drawToCache(key, bg, fg, ext, restrictToCellHeight, domContainer);\n      cacheMap.set(key, bg, fg, ext, $glyph);\n    }\n    return $glyph;\n  }\n\n  private _getColorFromAnsiIndex(idx: number): IColor {\n    if (idx >= this._config.colors.ansi.length) {\n      throw new Error('No color found for idx ' + idx);\n    }\n    return this._config.colors.ansi[idx];\n  }\n\n  private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean, dim: boolean): IColor {\n    if (this._config.allowTransparency) {\n      // The background color might have some transparency, so we need to render it as fully\n      // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice\n      // around the anti-aliased edges of the glyph, and it would look too dark.\n      return NULL_COLOR;\n    }\n\n    let result: IColor;\n    switch (bgColorMode) {\n      case Attributes.CM_P16:\n      case Attributes.CM_P256:\n        result = this._getColorFromAnsiIndex(bgColor);\n        break;\n      case Attributes.CM_RGB:\n        const arr = AttributeData.toColorRGB(bgColor);\n        result = channels.toColor(arr[0], arr[1], arr[2]);\n        break;\n      case Attributes.CM_DEFAULT:\n      default:\n        if (inverse) {\n          result = color.opaque(this._config.colors.foreground);\n        } else {\n          result = this._config.colors.background;\n        }\n        break;\n    }\n\n    // Ignore alpha channel when allowTransparency is false\n    if (!this._config.allowTransparency) {\n      result = color.opaque(result);\n    }\n\n    return result;\n  }\n\n  private _getForegroundColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, dim: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): IColor {\n    const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, dim, excludeFromContrastRatioDemands);\n    if (minimumContrastColor) {\n      return minimumContrastColor;\n    }\n\n    let result: IColor;\n    switch (fgColorMode) {\n      case Attributes.CM_P16:\n      case Attributes.CM_P256:\n        if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {\n          fgColor += 8;\n        }\n        result = this._getColorFromAnsiIndex(fgColor);\n        break;\n      case Attributes.CM_RGB:\n        const arr = AttributeData.toColorRGB(fgColor);\n        result = channels.toColor(arr[0], arr[1], arr[2]);\n        break;\n      case Attributes.CM_DEFAULT:\n      default:\n        if (inverse) {\n          result = this._config.colors.background;\n        } else {\n          result = this._config.colors.foreground;\n        }\n    }\n\n    // Always use an opaque color regardless of allowTransparency\n    if (this._config.allowTransparency) {\n      result = color.opaque(result);\n    }\n\n    // Apply dim to the color, opacity is fine to use for the foreground color\n    if (dim) {\n      result = color.multiplyOpacity(result, DIM_OPACITY);\n    }\n\n    return result;\n  }\n\n  private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {\n    switch (bgColorMode) {\n      case Attributes.CM_P16:\n      case Attributes.CM_P256:\n        return this._getColorFromAnsiIndex(bgColor).rgba;\n      case Attributes.CM_RGB:\n        return bgColor << 8;\n      case Attributes.CM_DEFAULT:\n      default:\n        if (inverse) {\n          return this._config.colors.foreground.rgba;\n        }\n        return this._config.colors.background.rgba;\n    }\n  }\n\n  private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {\n    switch (fgColorMode) {\n      case Attributes.CM_P16:\n      case Attributes.CM_P256:\n        if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {\n          fgColor += 8;\n        }\n        return this._getColorFromAnsiIndex(fgColor).rgba;\n      case Attributes.CM_RGB:\n        return fgColor << 8;\n      case Attributes.CM_DEFAULT:\n      default:\n        if (inverse) {\n          return this._config.colors.background.rgba;\n        }\n        return this._config.colors.foreground.rgba;\n    }\n  }\n\n  private _getMinimumContrastColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, dim: boolean, excludeFromContrastRatioDemands: boolean): IColor | undefined {\n    if (this._config.minimumContrastRatio === 1 || excludeFromContrastRatioDemands) {\n      return undefined;\n    }\n\n    // Try get from cache first\n    const cache = this._getContrastCache(dim);\n    const adjustedColor = cache.getColor(bg, fg);\n    if (adjustedColor !== undefined) {\n      return adjustedColor ?? undefined;\n    }\n\n    const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);\n    const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);\n    // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from\n    // non-dim cells\n    const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio / (dim ? 2 : 1));\n\n    if (!result) {\n      cache.setColor(bg, fg, null);\n      return undefined;\n    }\n\n    const color = channels.toColor(\n      (result >> 24) & 0xFF,\n      (result >> 16) & 0xFF,\n      (result >> 8) & 0xFF\n    );\n    cache.setColor(bg, fg, color);\n\n    return color;\n  }\n\n  private _getContrastCache(dim: boolean): IColorContrastCache {\n    if (dim) {\n      return this._config.colors.halfContrastCache;\n    }\n    return this._config.colors.contrastCache;\n  }\n\n  private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {\n    const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;\n\n    // Uncomment for debugging\n    // console.log(`draw to cache \"${chars}\"`, bg, fg, ext);\n\n    // Attach the canvas to the DOM in order to inherit font-feature-settings\n    // from the parent elements. This is necessary for ligatures and variants to\n    // work.\n    if (domContainer && this._tmpCanvas.parentElement !== domContainer) {\n      this._tmpCanvas.style.display = 'none';\n      domContainer.append(this._tmpCanvas);\n    }\n\n    // Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used\n    // to draw the glyph to the canvas as well as to restrict the bounding box search to ensure\n    // giant ligatures (eg. =====>) don't impact overall performance.\n    const allowedWidth = Math.min(this._config.deviceCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2, this._config.deviceMaxTextureSize);\n    if (this._tmpCanvas.width < allowedWidth) {\n      this._tmpCanvas.width = allowedWidth;\n    }\n    // Include line height when drawing glyphs\n    const allowedHeight = Math.min(this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 4, this._textureSize);\n    if (this._tmpCanvas.height < allowedHeight) {\n      this._tmpCanvas.height = allowedHeight;\n    }\n    this._tmpCtx.save();\n\n    this._workAttributeData.fg = fg;\n    this._workAttributeData.bg = bg;\n    this._workAttributeData.extended.ext = ext;\n\n    const invisible = !!this._workAttributeData.isInvisible();\n    if (invisible) {\n      return NULL_RASTERIZED_GLYPH;\n    }\n\n    const bold = !!this._workAttributeData.isBold();\n    const inverse = !!this._workAttributeData.isInverse();\n    const dim = !!this._workAttributeData.isDim();\n    const italic = !!this._workAttributeData.isItalic();\n    const underline = !!this._workAttributeData.isUnderline();\n    const strikethrough = !!this._workAttributeData.isStrikethrough();\n    const overline = !!this._workAttributeData.isOverline();\n    let fgColor = this._workAttributeData.getFgColor();\n    let fgColorMode = this._workAttributeData.getFgColorMode();\n    let bgColor = this._workAttributeData.getBgColor();\n    let bgColorMode = this._workAttributeData.getBgColorMode();\n    if (inverse) {\n      const temp = fgColor;\n      fgColor = bgColor;\n      bgColor = temp;\n      const temp2 = fgColorMode;\n      fgColorMode = bgColorMode;\n      bgColorMode = temp2;\n    }\n\n    // draw the background\n    const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse, dim);\n    // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha,\n    // regardless of transparency in backgroundColor\n    this._tmpCtx.globalCompositeOperation = 'copy';\n    this._tmpCtx.fillStyle = backgroundColor.css;\n    this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);\n    this._tmpCtx.globalCompositeOperation = 'source-over';\n\n    // draw the foreground/glyph\n    const fontWeight = bold ? this._config.fontWeightBold : this._config.fontWeight;\n    const fontStyle = italic ? 'italic' : '';\n    this._tmpCtx.font =\n      `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;\n    this._tmpCtx.textBaseline = TEXT_BASELINE;\n\n    const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));\n    const restrictedPowerlineGlyph = chars.length === 1 && isRestrictedPowerlineGlyph(chars.charCodeAt(0));\n    const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, treatGlyphAsBackgroundColor(chars.charCodeAt(0)));\n    this._tmpCtx.fillStyle = foregroundColor.css;\n\n    // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)\n    const padding = restrictedPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING * 2;\n\n    // Draw custom characters if applicable\n    let customGlyph = false;\n    if (this._config.customGlyphs !== false) {\n      const variantOffset = this._workAttributeData.getUnderlineVariantOffset();\n      customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);\n    }\n\n    // Whether to clear pixels based on a threshold difference between the glyph color and the\n    // background color. This should be disabled when the glyph contains multiple colors such as\n    // underline colors to prevent important colors could get cleared.\n    let enableClearThresholdCheck = !powerlineGlyph;\n\n    let chWidth: number;\n    if (typeof codeOrChars === 'number') {\n      chWidth = this._unicodeService.wcwidth(codeOrChars);\n    } else {\n      chWidth = this._unicodeService.getStringCellWidth(codeOrChars);\n    }\n\n    // Draw underline\n    if (underline) {\n      this._tmpCtx.save();\n      const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 15));\n      // When the line width is odd, draw at a 0.5 position\n      const yOffset = lineWidth % 2 === 1 ? 0.5 : 0;\n      this._tmpCtx.lineWidth = lineWidth;\n\n      // Underline color\n      if (this._workAttributeData.isUnderlineColorDefault()) {\n        this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle;\n      } else if (this._workAttributeData.isUnderlineColorRGB()) {\n        enableClearThresholdCheck = false;\n        this._tmpCtx.strokeStyle = `rgb(${AttributeData.toColorRGB(this._workAttributeData.getUnderlineColor()).join(',')})`;\n      } else {\n        enableClearThresholdCheck = false;\n        let fg = this._workAttributeData.getUnderlineColor();\n        if (this._config.drawBoldTextInBrightColors && this._workAttributeData.isBold() && fg < 8) {\n          fg += 8;\n        }\n        this._tmpCtx.strokeStyle = this._getColorFromAnsiIndex(fg).css;\n      }\n      this._tmpCtx.fillStyle = this._tmpCtx.strokeStyle;\n\n      // Underline style/stroke\n      this._tmpCtx.beginPath();\n      const xLeft = padding;\n      const yTopDefault = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);\n      const yBotDefault = yTopDefault + lineWidth * 2;\n      let nextOffset = this._workAttributeData.getUnderlineVariantOffset();\n      let yTop = 0;\n      let yBot = 0;\n\n      for (let i = 0; i < chWidth; i++) {\n        let wasFilled = false;\n        this._tmpCtx.save();\n        yTop = yTopDefault;\n        yBot = yBotDefault;\n        const xChLeft = xLeft + i * this._config.deviceCellWidth;\n        const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth;\n        switch (this._workAttributeData.extended.underlineStyle) {\n          case UnderlineStyle.DOUBLE:\n            this._tmpCtx.moveTo(xChLeft, yTopDefault);\n            this._tmpCtx.lineTo(xChRight, yTopDefault);\n            this._tmpCtx.moveTo(xChLeft, yBotDefault);\n            this._tmpCtx.lineTo(xChRight, yBotDefault);\n            break;\n          case UnderlineStyle.CURLY:\n            yTop = this._config.deviceCharHeight + 1;\n            yBot = yTop + 3 * this._config.devicePixelRatio;\n\n            const clipRegion = new Path2D();\n            clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);\n            this._tmpCtx.clip(clipRegion);\n\n            // Draw a zigzag pattern, this is derived from the SVG used in monaco for the same\n            // style. The viewbox is 6x3 so scale it using that.\n            const cellW = this._config.deviceCellWidth;\n            const curlyH = (yBot - yTop);\n            const scaleX = cellW / 6;\n            const scaleY = curlyH / 3;\n\n            const polygons: number[][] = [\n              [0, 2, 1, 3, 2.4, 3, 0, 0.6],\n              [5.5, 0, 2.5, 3, 1.1, 3, 4.1, 0],\n              [4, 0, 6, 2, 6, 0.6, 5.4, 0],\n            ];\n\n            for (const polygon of polygons) {\n              this._tmpCtx.beginPath();\n              for (let i = 0; i < polygon.length; i += 2) {\n                const x = xChLeft + polygon[i] * scaleX;\n                const y = yBot - polygon[i + 1] * scaleY;\n                if (i === 0) {\n                  this._tmpCtx.moveTo(x, y);\n                } else {\n                  this._tmpCtx.lineTo(x, y);\n                }\n              }\n              this._tmpCtx.closePath();\n              this._tmpCtx.fill();\n            }\n            wasFilled = true;\n            break;\n          case UnderlineStyle.DOTTED:\n            const offsetWidth = nextOffset === 0 ? 0 :\n              (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset);\n              // a line and a gap.\n            const isLineStart = nextOffset >= lineWidth ? false : true;\n            if (isLineStart === false || offsetWidth === 0) {\n              this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);\n              this._tmpCtx.moveTo(xChLeft + offsetWidth, yTopDefault);\n              this._tmpCtx.lineTo(xChRight, yTopDefault);\n            } else {\n              this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);\n              this._tmpCtx.moveTo(xChLeft, yTopDefault);\n              this._tmpCtx.lineTo(xChLeft + offsetWidth, yTopDefault);\n              this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTopDefault);\n              this._tmpCtx.lineTo(xChRight, yTopDefault);\n            }\n            nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);\n            break;\n          case UnderlineStyle.DASHED:\n            const lineRatio = 0.6;\n            const gapRatio = 0.3;\n            // End line ratio is approximately equal to 0.1\n            const xChWidth = xChRight - xChLeft;\n            const line = Math.floor(lineRatio * xChWidth);\n            const gap = Math.floor(gapRatio * xChWidth);\n            const end = xChWidth - line - gap;\n            this._tmpCtx.setLineDash([line, gap, end]);\n            this._tmpCtx.moveTo(xChLeft, yTopDefault);\n            this._tmpCtx.lineTo(xChRight, yTopDefault);\n            break;\n          case UnderlineStyle.SINGLE:\n          default:\n            this._tmpCtx.moveTo(xChLeft, yTopDefault);\n            this._tmpCtx.lineTo(xChRight, yTopDefault);\n            break;\n        }\n        if (!wasFilled) {\n          this._tmpCtx.stroke();\n        }\n        this._tmpCtx.restore();\n      }\n      this._tmpCtx.restore();\n\n      // Draw stroke in the background color for non custom characters in order to give an outline\n      // between the text and the underline. Only do this when font size is >= 12 as the underline\n      // looks odd when the font size is too small\n      if (!customGlyph && this._config.fontSize >= 12) {\n        // This only works when transparency is disabled because it's not clear how to clear stroked\n        // text\n        if (!this._config.allowTransparency && chars !== ' ') {\n          // Measure the text, only draw the stroke if there is a descent beyond an alphabetic text\n          // baseline\n          this._tmpCtx.save();\n          this._tmpCtx.textBaseline = 'alphabetic';\n          const metrics = this._tmpCtx.measureText(chars);\n          this._tmpCtx.restore();\n          if ('actualBoundingBoxDescent' in metrics && metrics.actualBoundingBoxDescent > 0) {\n            // This translates to 1/2 the line width in either direction\n            this._tmpCtx.save();\n            // Clip the region to only draw in valid pixels near the underline to avoid a slight\n            // outline around the whole glyph, as well as additional pixels in the glyph at the top\n            // which would increase GPU memory demands\n            const clipRegion = new Path2D();\n            clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth * chWidth, yBot - yTop + Math.ceil(lineWidth / 2));\n            this._tmpCtx.clip(clipRegion);\n            this._tmpCtx.lineWidth = this._config.devicePixelRatio * 3;\n            this._tmpCtx.strokeStyle = backgroundColor.css;\n            this._tmpCtx.strokeText(chars, padding, padding + this._config.deviceCharHeight);\n            this._tmpCtx.restore();\n          }\n        }\n      }\n    }\n\n    // Overline\n    if (overline) {\n      const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 15));\n      const yOffset = lineWidth % 2 === 1 ? 0.5 : 0;\n      this._tmpCtx.lineWidth = lineWidth;\n      this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle;\n      this._tmpCtx.beginPath();\n      this._tmpCtx.moveTo(padding, padding + yOffset);\n      this._tmpCtx.lineTo(padding + this._config.deviceCharWidth * chWidth, padding + yOffset);\n      this._tmpCtx.stroke();\n    }\n\n    // Draw the character\n    if (!customGlyph) {\n      this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight);\n    }\n\n    // If this character is underscore and beyond the cell bounds, shift it up until it is visible\n    // even on the bottom row, try for a maximum of 5 pixels.\n    if (chars === '_' && !this._config.allowTransparency) {\n      let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck);\n      if (isBeyondCellBounds) {\n        for (let offset = 1; offset <= 5; offset++) {\n          this._tmpCtx.save();\n          this._tmpCtx.fillStyle = backgroundColor.css;\n          this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);\n          this._tmpCtx.restore();\n          this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight - offset);\n          isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck);\n          if (!isBeyondCellBounds) {\n            break;\n          }\n        }\n      }\n    }\n\n    // Draw strokethrough\n    if (strikethrough) {\n      const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 10));\n      const yOffset = this._tmpCtx.lineWidth % 2 === 1 ? 0.5 : 0; // When the width is odd, draw at 0.5 position\n      this._tmpCtx.lineWidth = lineWidth;\n      this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle;\n      this._tmpCtx.beginPath();\n      this._tmpCtx.moveTo(padding, padding + Math.floor(this._config.deviceCharHeight / 2) - yOffset);\n      this._tmpCtx.lineTo(padding + this._config.deviceCharWidth * chWidth, padding + Math.floor(this._config.deviceCharHeight / 2) - yOffset);\n      this._tmpCtx.stroke();\n    }\n\n    this._tmpCtx.restore();\n\n    // clear the background from the character to avoid issues with drawing over the previous\n    // character if it extends past it's bounds\n    const imageData = this._tmpCtx.getImageData(\n      0, 0, this._tmpCanvas.width, this._tmpCanvas.height\n    );\n\n    // Clear out the background color and determine if the glyph is empty.\n    let isEmpty: boolean;\n    if (!this._config.allowTransparency) {\n      isEmpty = clearColor(imageData, backgroundColor, foregroundColor, enableClearThresholdCheck);\n    } else {\n      isEmpty = checkCompletelyTransparent(imageData);\n    }\n\n    // Handle empty glyphs\n    if (isEmpty) {\n      return NULL_RASTERIZED_GLYPH;\n    }\n\n    const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, restrictedPowerlineGlyph, customGlyph, padding);\n\n    // Find the best atlas row to use\n    let activePage: AtlasPage;\n    let activeRow: ICharAtlasActiveRow;\n    while (true) {\n      // If there are no active pages (the last smallest 4 were merged), create a new one\n      if (this._activePages.length === 0) {\n        const newPage = this._createNewPage();\n        activePage = newPage;\n        activeRow = newPage.currentRow;\n        activeRow.height = rasterizedGlyph.size.y;\n        break;\n      }\n\n      // Get the best current row from all active pages\n      activePage = this._activePages[this._activePages.length - 1];\n      activeRow = activePage.currentRow;\n      for (const p of this._activePages) {\n        if (rasterizedGlyph.size.y <= p.currentRow.height) {\n          activePage = p;\n          activeRow = p.currentRow;\n        }\n      }\n\n      // TODO: This algorithm could be simplified:\n      // - Search for the page with ROW_PIXEL_THRESHOLD in mind\n      // - Keep track of current/fixed rows in a Map\n\n      // Replace the best current row with a fixed row if there is one at least as good as the\n      // current row. Search in reverse to prioritize filling in older pages.\n      for (let i = this._activePages.length - 1; i >= 0; i--) {\n        for (const row of this._activePages[i].fixedRows) {\n          if (row.height <= activeRow.height && rasterizedGlyph.size.y <= row.height) {\n            activePage = this._activePages[i];\n            activeRow = row;\n          }\n        }\n      }\n\n      // Create a new page for oversized glyphs as they come up\n      if (rasterizedGlyph.size.x > this._textureSize) {\n        if (!this._overflowSizePage) {\n          this._overflowSizePage = new AtlasPage(this._document, this._config.deviceMaxTextureSize);\n          this.pages.push(this._overflowSizePage);\n\n          // Request the model to be cleared to refresh all texture pages.\n          this._requestClearModel = true;\n          this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas);\n        }\n        activePage = this._overflowSizePage;\n        activeRow = this._overflowSizePage.currentRow;\n        // Move to next row if necessary\n        if (activeRow.x + rasterizedGlyph.size.x >= activePage.canvas.width) {\n          activeRow.x = 0;\n          activeRow.y += activeRow.height;\n          activeRow.height = 0;\n        }\n        break;\n      }\n\n      // Create a new page if too much vertical space would be wasted or there is not enough room\n      // left in the page. The previous active row will become fixed in the process as it now has a\n      // fixed height\n      if (activeRow.y + rasterizedGlyph.size.y >= activePage.canvas.height || activeRow.height > rasterizedGlyph.size.y + Constants.ROW_PIXEL_THRESHOLD) {\n        // Create the new fixed height row, creating a new page if there isn't enough room on the\n        // current page\n        let wasPageAndRowFound = false;\n        if (activePage.currentRow.y + activePage.currentRow.height + rasterizedGlyph.size.y >= activePage.canvas.height) {\n          // Find the first page with room to create the new row on\n          let candidatePage: AtlasPage | undefined;\n\n          for (const p of this._activePages) {\n            if (p.currentRow.y + p.currentRow.height + rasterizedGlyph.size.y < p.canvas.height) {\n              candidatePage = p;\n              break;\n            }\n          }\n          if (candidatePage) {\n            activePage = candidatePage;\n          } else {\n            // Before creating a new atlas page that would trigger a page merge, check if the\n            // current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will\n            // improve texture utilization by using the available space before the page is merged\n            // and becomes static.\n            if (\n              TextureAtlas.maxAtlasPages &&\n              this._pages.length >= TextureAtlas.maxAtlasPages &&\n              activeRow.y + rasterizedGlyph.size.y <= activePage.canvas.height &&\n              activeRow.height >= rasterizedGlyph.size.y &&\n              activeRow.x + rasterizedGlyph.size.x <= activePage.canvas.width\n            ) {\n              // activePage and activeRow is already valid\n              wasPageAndRowFound = true;\n            } else {\n              // Create a new page if there is no room\n              const newPage = this._createNewPage();\n              activePage = newPage;\n              activeRow = newPage.currentRow;\n              activeRow.height = rasterizedGlyph.size.y;\n              wasPageAndRowFound = true;\n            }\n          }\n        }\n        if (!wasPageAndRowFound) {\n          // Fix the current row as the new row is being added below\n          if (activePage.currentRow.height > 0) {\n            activePage.fixedRows.push(activePage.currentRow);\n          }\n          activeRow = {\n            x: 0,\n            y: activePage.currentRow.y + activePage.currentRow.height,\n            height: rasterizedGlyph.size.y\n          };\n          activePage.fixedRows.push(activeRow);\n\n          // Create the new current row below the new fixed height row\n          activePage.currentRow = {\n            x: 0,\n            y: activeRow.y + activeRow.height,\n            height: 0\n          };\n        }\n        // TODO: Remove pages from _activePages when all rows are filled\n      }\n\n      // Exit the loop if there is enough room in the row\n      if (activeRow.x + rasterizedGlyph.size.x <= activePage.canvas.width) {\n        break;\n      }\n\n      // If there is not enough room in the current row, finish it and try again\n      if (activeRow === activePage.currentRow) {\n        activeRow.x = 0;\n        activeRow.y += activeRow.height;\n        activeRow.height = 0;\n      } else {\n        activePage.fixedRows.splice(activePage.fixedRows.indexOf(activeRow), 1);\n      }\n    }\n\n    // Record texture position\n    rasterizedGlyph.texturePage = this._pages.indexOf(activePage);\n    rasterizedGlyph.texturePosition.x = activeRow.x;\n    rasterizedGlyph.texturePosition.y = activeRow.y;\n    rasterizedGlyph.texturePositionClipSpace.x = activeRow.x / activePage.canvas.width;\n    rasterizedGlyph.texturePositionClipSpace.y = activeRow.y / activePage.canvas.height;\n\n    // Fix the clipspace position as pages may be of differing size\n    rasterizedGlyph.sizeClipSpace.x /= activePage.canvas.width;\n    rasterizedGlyph.sizeClipSpace.y /= activePage.canvas.height;\n\n    // Update atlas current row, for fixed rows the glyph height will never be larger than the row\n    // height\n    activeRow.height = Math.max(activeRow.height, rasterizedGlyph.size.y);\n    activeRow.x += rasterizedGlyph.size.x;\n\n    // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us\n    activePage.ctx.putImageData(\n      imageData,\n      rasterizedGlyph.texturePosition.x - this._workBoundingBox.left,\n      rasterizedGlyph.texturePosition.y - this._workBoundingBox.top,\n      this._workBoundingBox.left,\n      this._workBoundingBox.top,\n      rasterizedGlyph.size.x,\n      rasterizedGlyph.size.y\n    );\n    activePage.addGlyph(rasterizedGlyph);\n    activePage.version++;\n\n    return rasterizedGlyph;\n  }\n\n  /**\n   * Given an ImageData object, find the bounding box of the non-transparent\n   * portion of the texture and return an IRasterizedGlyph with these\n   * dimensions.\n   * @param imageData The image data to read.\n   * @param boundingBox An IBoundingBox to put the clipped bounding box values.\n   */\n  private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph {\n    boundingBox.top = 0;\n    const height = restrictedGlyph ? this._config.deviceCellHeight : this._tmpCanvas.height;\n    const width = restrictedGlyph ? this._config.deviceCellWidth : allowedWidth;\n    let found = false;\n    for (let y = 0; y < height; y++) {\n      for (let x = 0; x < width; x++) {\n        const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;\n        if (imageData.data[alphaOffset] !== 0) {\n          boundingBox.top = y;\n          found = true;\n          break;\n        }\n      }\n      if (found) {\n        break;\n      }\n    }\n    boundingBox.left = 0;\n    found = false;\n    for (let x = 0; x < padding + width; x++) {\n      for (let y = 0; y < height; y++) {\n        const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;\n        if (imageData.data[alphaOffset] !== 0) {\n          boundingBox.left = x;\n          found = true;\n          break;\n        }\n      }\n      if (found) {\n        break;\n      }\n    }\n    boundingBox.right = width;\n    found = false;\n    for (let x = padding + width - 1; x >= padding; x--) {\n      for (let y = 0; y < height; y++) {\n        const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;\n        if (imageData.data[alphaOffset] !== 0) {\n          boundingBox.right = x;\n          found = true;\n          break;\n        }\n      }\n      if (found) {\n        break;\n      }\n    }\n    boundingBox.bottom = height;\n    found = false;\n    for (let y = height - 1; y >= 0; y--) {\n      for (let x = 0; x < width; x++) {\n        const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;\n        if (imageData.data[alphaOffset] !== 0) {\n          boundingBox.bottom = y;\n          found = true;\n          break;\n        }\n      }\n      if (found) {\n        break;\n      }\n    }\n    return {\n      texturePage: 0,\n      texturePosition: { x: 0, y: 0 },\n      texturePositionClipSpace: { x: 0, y: 0 },\n      size: {\n        x: boundingBox.right - boundingBox.left + 1,\n        y: boundingBox.bottom - boundingBox.top + 1\n      },\n      sizeClipSpace: {\n        x: (boundingBox.right - boundingBox.left + 1),\n        y: (boundingBox.bottom - boundingBox.top + 1)\n      },\n      offset: {\n        x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.deviceCellWidth - this._config.deviceCharWidth) / 2) : 0),\n        y: -boundingBox.top + padding + ((restrictedGlyph || customGlyph) ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.deviceCellHeight - this._config.deviceCharHeight) / 2) : 0)\n      }\n    };\n  }\n}\n\nclass AtlasPage {\n  public readonly canvas: HTMLCanvasElement;\n  public readonly ctx: CanvasRenderingContext2D;\n\n  private _usedPixels: number = 0;\n  public get percentageUsed(): number { return this._usedPixels / (this.canvas.width * this.canvas.height); }\n\n  private readonly _glyphs: IRasterizedGlyph[] = [];\n  public get glyphs(): ReadonlyArray<IRasterizedGlyph> { return this._glyphs; }\n  public addGlyph(glyph: IRasterizedGlyph): void {\n    this._glyphs.push(glyph);\n    this._usedPixels += glyph.size.x * glyph.size.y;\n  }\n\n  /**\n   * Used to check whether the canvas of the atlas page has changed.\n   */\n  public version = 0;\n\n  // Texture atlas current positioning data. The texture packing strategy used is to fill from\n  // left-to-right and top-to-bottom. When the glyph being written is less than half of the current\n  // row's height, the following happens:\n  //\n  // - The current row becomes the fixed height row A\n  // - A new fixed height row B the exact size of the glyph is created below the current row\n  // - A new dynamic height current row is created below B\n  //\n  // This strategy does a good job preventing space being wasted for very short glyphs such as\n  // underscores, hyphens etc. or those with underlines rendered.\n  public currentRow: ICharAtlasActiveRow = {\n    x: 0,\n    y: 0,\n    height: 0\n  };\n  public readonly fixedRows: ICharAtlasActiveRow[] = [];\n\n  constructor(\n    document: Document,\n    size: number,\n    sourcePages?: AtlasPage[]\n  ) {\n    if (sourcePages) {\n      for (const p of sourcePages) {\n        this._glyphs.push(...p.glyphs);\n        this._usedPixels += p._usedPixels;\n      }\n    }\n    this.canvas = createCanvas(document, size, size);\n    // The canvas needs alpha because we use clearColor to convert the background color to alpha.\n    // It might also contain some characters with transparent backgrounds if allowTransparency is\n    // set.\n    this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));\n  }\n\n  public clear(): void {\n    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n    this.currentRow.x = 0;\n    this.currentRow.y = 0;\n    this.currentRow.height = 0;\n    this.fixedRows.length = 0;\n    this.version++;\n  }\n}\n\n/**\n * Makes a particular rgb color and colors that are nearly the same in an ImageData completely\n * transparent.\n * @returns True if the result is \"empty\", meaning all pixels are fully transparent.\n */\nfunction clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThresholdCheck: boolean): boolean {\n  // Get color channels\n  const r = bg.rgba >>> 24;\n  const g = bg.rgba >>> 16 & 0xFF;\n  const b = bg.rgba >>> 8 & 0xFF;\n  const fgR = fg.rgba >>> 24;\n  const fgG = fg.rgba >>> 16 & 0xFF;\n  const fgB = fg.rgba >>> 8 & 0xFF;\n\n  // Calculate a threshold that when below a color will be treated as transpart when the sum of\n  // channel value differs. This helps improve rendering when glyphs overlap with others. This\n  // threshold is calculated relative to the difference between the background and foreground to\n  // ensure important details of the glyph are always shown, even when the contrast ratio is low.\n  // The number 12 is largely arbitrary to ensure the pixels that escape the cell in the test case\n  // were covered (fg=#8ae234, bg=#c4a000).\n  const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);\n\n  // Set alpha channel of relevent pixels to 0\n  let isEmpty = true;\n  for (let offset = 0; offset < imageData.data.length; offset += 4) {\n    // Check exact match\n    if (imageData.data[offset] === r &&\n        imageData.data[offset + 1] === g &&\n        imageData.data[offset + 2] === b) {\n      imageData.data[offset + 3] = 0;\n    } else {\n      // Check the threshold based difference\n      if (enableThresholdCheck &&\n          (Math.abs(imageData.data[offset] - r) +\n          Math.abs(imageData.data[offset + 1] - g) +\n          Math.abs(imageData.data[offset + 2] - b)) < threshold) {\n        imageData.data[offset + 3] = 0;\n      } else {\n        isEmpty = false;\n      }\n    }\n  }\n\n  return isEmpty;\n}\n\nfunction checkCompletelyTransparent(imageData: ImageData): boolean {\n  for (let offset = 0; offset < imageData.data.length; offset += 4) {\n    if (imageData.data[offset + 3] > 0) {\n      return false;\n    }\n  }\n  return true;\n}\n\nfunction createCanvas(document: Document, width: number, height: number): HTMLCanvasElement {\n  const canvas = document.createElement('canvas');\n  canvas.width = width;\n  canvas.height = height;\n  return canvas;\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/TypedArray.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { sliceFallback } from './TypedArray';\n\ntype TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;\n\nfunction deepEquals(a: TypedArray, b: TypedArray): void {\n  assert.equal(a.length, b.length);\n  for (let i = 0; i < a.length; ++i) {\n    assert.equal(a[i], b[i]);\n  }\n}\n\ndescribe('polyfill conformance tests', function(): void {\n  describe('TypedArray.slice', () => {\n    describe('should work with all typed array types', () => {\n      it('Uint8Array', () => {\n        const a = new Uint8Array(5);\n        deepEquals(sliceFallback(a, 2), a.slice(2));\n        deepEquals(sliceFallback(a, 65535), a.slice(65535));\n        deepEquals(sliceFallback(a, -1), a.slice(-1));\n      });\n      it('Uint16Array', () => {\n        const u161 = new Uint16Array(5);\n        const u162 = new Uint16Array(5);\n        deepEquals(sliceFallback(u161, 2), u162.slice(2));\n        deepEquals(sliceFallback(u161, 65535), u162.slice(65535));\n        deepEquals(sliceFallback(u161, -1), u162.slice(-1));\n      });\n      it('Uint32Array', () => {\n        const u321 = new Uint32Array(5);\n        const u322 = new Uint32Array(5);\n        deepEquals(sliceFallback(u321, 2), u322.slice(2));\n        deepEquals(sliceFallback(u321, 65537), u322.slice(65537));\n        deepEquals(sliceFallback(u321, -1), u322.slice(-1));\n      });\n      it('Int8Array', () => {\n        const i81 = new Int8Array(5);\n        const i82 = new Int8Array(5);\n        deepEquals(sliceFallback(i81, 2), i82.slice(2));\n        deepEquals(sliceFallback(i81, 65537), i82.slice(65537));\n        deepEquals(sliceFallback(i81, -1), i82.slice(-1));\n      });\n      it('Int16Array', () => {\n        const i161 = new Int16Array(5);\n        const i162 = new Int16Array(5);\n        deepEquals(sliceFallback(i161, 2), i162.slice(2));\n        deepEquals(sliceFallback(i161, 65535), i162.slice(65535));\n        deepEquals(sliceFallback(i161, -1), i162.slice(-1));\n      });\n      it('Int32Array', () => {\n        const i321 = new Int32Array(5);\n        const i322 = new Int32Array(5);\n        deepEquals(sliceFallback(i321, 2), i322.slice(2));\n        deepEquals(sliceFallback(i321, 65537), i322.slice(65537));\n        deepEquals(sliceFallback(i321, -1), i322.slice(-1));\n      });\n      it('Float32Array', () => {\n        const f321 = new Float32Array(5);\n        const f322 = new Float32Array(5);\n        deepEquals(sliceFallback(f321, 2), f322.slice(2));\n        deepEquals(sliceFallback(f321, 65537), f322.slice(65537));\n        deepEquals(sliceFallback(f321, -1), f322.slice(-1));\n      });\n      it('Float64Array', () => {\n        const f641 = new Float64Array(5);\n        const f642 = new Float64Array(5);\n        deepEquals(sliceFallback(f641, 2), f642.slice(2));\n        deepEquals(sliceFallback(f641, 65537), f642.slice(65537));\n        deepEquals(sliceFallback(f641, -1), f642.slice(-1));\n      });\n      it('Uint8ClampedArray', () => {\n        const u8Clamped1 = new Uint8ClampedArray(5);\n        const u8Clamped2 = new Uint8ClampedArray(5);\n        deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2));\n        deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537));\n        deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1));\n      });\n    });\n    it('start', () => {\n      const arr = new Uint32Array([1, 2, 3, 4, 5]);\n      deepEquals(sliceFallback(arr, -1), arr.slice(-1));\n      deepEquals(sliceFallback(arr, 0), arr.slice(0));\n      deepEquals(sliceFallback(arr, 1), arr.slice(1));\n      deepEquals(sliceFallback(arr, 2), arr.slice(2));\n      deepEquals(sliceFallback(arr, 3), arr.slice(3));\n      deepEquals(sliceFallback(arr, 4), arr.slice(4));\n      deepEquals(sliceFallback(arr, 5), arr.slice(5));\n    });\n    it('end', () => {\n      const arr = new Uint32Array([1, 2, 3, 4, 5]);\n      deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2));\n      deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2));\n      deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2));\n      deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2));\n      deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2));\n      deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2));\n      deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2));\n\n      deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3));\n      deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3));\n      deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3));\n      deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3));\n      deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3));\n      deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3));\n      deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3));\n\n      deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8));\n      deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8));\n      deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8));\n      deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8));\n      deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8));\n      deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8));\n      deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8));\n    });\n  });\n});\n"
  },
  {
    "path": "addons/addon-webgl/src/TypedArray.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;\n\nexport function slice<T extends TypedArray>(array: T, start?: number, end?: number): T {\n  // all modern engines that support .slice\n  if (array.slice) {\n    return array.slice(start, end) as T;\n  }\n  return sliceFallback(array, start, end);\n}\n\nexport function sliceFallback<T extends TypedArray>(array: T, start: number = 0, end: number = array.length): T {\n  if (start < 0) {\n    start = (array.length + start) % array.length;\n  }\n  if (end >= array.length) {\n    end = array.length;\n  } else {\n    end = (array.length + end) % array.length;\n  }\n  start = Math.min(start, end);\n\n  const result: T = new (array.constructor as any)(end - start);\n  for (let i = 0; i < end - start; ++i) {\n    result[i] = array[i + start];\n  }\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/Types.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { FontWeight } from '@xterm/xterm';\nimport { IColorSet } from 'browser/Types';\nimport { ISelectionRenderModel } from 'browser/renderer/shared/Types';\nimport { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';\nimport type { IEvent } from 'common/Event';\n\nexport interface IRenderModel {\n  cells: Uint32Array;\n  lineLengths: Uint32Array;\n  selection: ISelectionRenderModel;\n  cursor?: ICursorRenderModel;\n}\n\nexport interface ICursorRenderModel {\n  x: number;\n  y: number;\n  width: number;\n  style: CursorStyle | CursorInactiveStyle;\n  cursorWidth: number;\n  dpr: number;\n}\n\nexport interface IWebGL2RenderingContext extends WebGLRenderingContext {\n  vertexAttribDivisor(index: number, divisor: number): void;\n  createVertexArray(): IWebGLVertexArrayObject;\n  bindVertexArray(vao: IWebGLVertexArrayObject): void;\n  drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;\n}\n\nexport interface IWebGLVertexArrayObject {\n}\n\nexport interface ICharAtlasConfig {\n  customGlyphs: boolean;\n  devicePixelRatio: number;\n  deviceMaxTextureSize: number;\n  letterSpacing: number;\n  lineHeight: number;\n  fontSize: number;\n  fontFamily: string;\n  fontWeight: FontWeight;\n  fontWeightBold: FontWeight;\n  deviceCellWidth: number;\n  deviceCellHeight: number;\n  deviceCharWidth: number;\n  deviceCharHeight: number;\n  allowTransparency: boolean;\n  drawBoldTextInBrightColors: boolean;\n  minimumContrastRatio: number;\n  colors: IColorSet;\n}\n\nexport interface ITextureAtlas extends IDisposable {\n  readonly pages: { canvas: HTMLCanvasElement, version: number }[];\n\n  onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;\n  onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;\n\n  /**\n   * Warm up the texture atlas, adding common glyphs to avoid slowing early frame.\n   */\n  warmUp(): void;\n\n  /**\n   * Call when a frame is being drawn, this will return true if the atlas was cleared to make room\n   * for a new set of glyphs.\n   */\n  beginFrame(): boolean;\n\n  /**\n   * Clear all glyphs from the texture atlas.\n   */\n  clearTexture(): void;\n  getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph;\n  getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph;\n}\n\n/**\n * Represents a rasterized glyph within a texture atlas. Some numbers are\n * tracked in CSS pixels as well in order to reduce calculations during the\n * render loop.\n */\nexport interface IRasterizedGlyph {\n  /**\n   * The x and y offset between the glyph's top/left and the top/left of a cell\n   * in pixels.\n   */\n  offset: IVector;\n  /**\n   * The index of the texture page that the glyph is on.\n   */\n  texturePage: number;\n  /**\n   * the x and y position of the glyph in the texture in pixels.\n   */\n  texturePosition: IVector;\n  /**\n   * the x and y position of the glyph in the texture in clip space coordinates.\n   */\n  texturePositionClipSpace: IVector;\n  /**\n   * The width and height of the glyph in the texture in pixels.\n   */\n  size: IVector;\n  /**\n   * The width and height of the glyph in the texture in clip space coordinates.\n   */\n  sizeClipSpace: IVector;\n}\n\nexport interface IVector {\n  x: number;\n  y: number;\n}\n\nexport interface IBoundingBox {\n  top: number;\n  left: number;\n  right: number;\n  bottom: number;\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/WebglAddon.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { ITerminalAddon, Terminal } from '@xterm/xterm';\nimport type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';\nimport { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';\nimport { ITerminal } from 'browser/Types';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { getSafariVersion, isSafari } from 'common/Platform';\nimport { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';\nimport { IWebGL2RenderingContext } from './Types';\nimport { WebglRenderer } from './WebglRenderer';\nimport { Emitter, EventUtils } from 'common/Event';\n\nexport class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {\n  private _terminal?: Terminal;\n  private _renderer?: WebglRenderer;\n\n  private readonly _onChangeTextureAtlas = this._register(new Emitter<HTMLCanvasElement>());\n  public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;\n  private readonly _onAddTextureAtlasCanvas = this._register(new Emitter<HTMLCanvasElement>());\n  public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;\n  private readonly _onRemoveTextureAtlasCanvas = this._register(new Emitter<HTMLCanvasElement>());\n  public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event;\n  private readonly _onContextLoss = this._register(new Emitter<void>());\n  public readonly onContextLoss = this._onContextLoss.event;\n\n  private readonly _customGlyphs: boolean;\n  private readonly _preserveDrawingBuffer?: boolean;\n\n  constructor(options?: IWebglAddonOptions) {\n    if (isSafari && getSafariVersion() < 16) {\n      // Perform an extra check to determine if Webgl2 is manually enabled in developer settings\n      const contextAttributes = {\n        antialias: false,\n        depth: false,\n        preserveDrawingBuffer: true\n      };\n      const gl = document.createElement('canvas').getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;\n      if (!gl) {\n        throw new Error('Webgl2 is only supported on Safari 16 and above');\n      }\n    }\n    super();\n    this._customGlyphs = options?.customGlyphs ?? true;\n    this._preserveDrawingBuffer = options?.preserveDrawingBuffer;\n  }\n\n  public activate(terminal: Terminal): void {\n    const core = (terminal as any)._core as ITerminal;\n    if (!terminal.element) {\n      this._register(core.onWillOpen(() => this.activate(terminal)));\n      return;\n    }\n\n    this._terminal = terminal;\n    const coreService: ICoreService = core.coreService;\n    const optionsService: IOptionsService = core.optionsService;\n\n    const unsafeCore = core as any;\n    const renderService: IRenderService = unsafeCore._renderService;\n    const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService;\n    const charSizeService: ICharSizeService = unsafeCore._charSizeService;\n    const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;\n    const decorationService: IDecorationService = unsafeCore._decorationService;\n    const themeService: IThemeService = unsafeCore._themeService;\n\n    this._renderer = this._register(new WebglRenderer(\n      terminal,\n      characterJoinerService,\n      charSizeService,\n      coreBrowserService,\n      coreService,\n      decorationService,\n      optionsService,\n      themeService,\n      this._customGlyphs,\n      this._preserveDrawingBuffer\n    ));\n    this._register(EventUtils.forward(this._renderer.onContextLoss, this._onContextLoss));\n    this._register(EventUtils.forward(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));\n    this._register(EventUtils.forward(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));\n    this._register(EventUtils.forward(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas));\n    renderService.setRenderer(this._renderer);\n\n    this._register(toDisposable(() => {\n      if ((this._terminal as any)._core._store._isDisposed) {\n        return;\n      }\n      const renderService: IRenderService = (this._terminal as any)._core._renderService;\n      renderService.setRenderer((this._terminal as any)._core._createRenderer());\n      renderService.handleResize(terminal.cols, terminal.rows);\n    }));\n  }\n\n  public get textureAtlas(): HTMLCanvasElement | undefined {\n    return this._renderer?.textureAtlas;\n  }\n\n  public clearTextureAtlas(): void {\n    this._renderer?.clearTextureAtlas();\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/WebglRenderer.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from 'browser/Types';\nimport { CellColorResolver } from './CellColorResolver';\nimport { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';\nimport { CursorBlinkStateManager } from './CursorBlinkStateManager';\nimport { observeDevicePixelDimensions } from './DevicePixelObserver';\nimport { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';\nimport { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';\nimport { CharData, IBufferLine, ICellData } from 'common/Types';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { CellData } from 'common/buffer/CellData';\nimport { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';\nimport { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';\nimport { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';\nimport { Terminal } from '@xterm/xterm';\nimport { GlyphRenderer } from './GlyphRenderer';\nimport { RectangleRenderer } from './RectangleRenderer';\nimport { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel';\nimport { IWebGL2RenderingContext, type ITextureAtlas } from './Types';\nimport { LinkRenderLayer } from './renderLayer/LinkRenderLayer';\nimport { IRenderLayer } from './renderLayer/Types';\nimport { Emitter, EventUtils } from 'common/Event';\nimport { addDisposableListener } from 'browser/Dom';\nimport { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\nimport { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';\n\nexport class WebglRenderer extends Disposable implements IRenderer {\n  private _renderLayers: IRenderLayer[];\n  private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();\n  private _textBlinkStateManager: TextBlinkStateManager;\n  private _charAtlasDisposable = this._register(new MutableDisposable());\n  private _charAtlas: ITextureAtlas | undefined;\n  private _devicePixelRatio: number;\n  private _deviceMaxTextureSize: number;\n  private _observerDisposable = this._register(new MutableDisposable());\n\n  private _model: RenderModel = new RenderModel();\n  private _rowHasBlinkingCells: boolean[] = [];\n  private _rowHasBlinkingCellsCount: number = 0;\n  private _workCell: ICellData = new CellData();\n  private _cellColorResolver: CellColorResolver;\n\n  private _canvas: HTMLCanvasElement;\n  private _gl: IWebGL2RenderingContext;\n  private _rectangleRenderer: MutableDisposable<RectangleRenderer> = this._register(new MutableDisposable());\n  private _glyphRenderer: MutableDisposable<GlyphRenderer> = this._register(new MutableDisposable());\n\n  public readonly dimensions: IRenderDimensions;\n\n  private _core: ITerminal;\n  private _isAttached: boolean;\n  private _contextRestorationTimeout: number | undefined;\n\n  private readonly _onChangeTextureAtlas = this._register(new Emitter<HTMLCanvasElement>());\n  public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;\n  private readonly _onAddTextureAtlasCanvas = this._register(new Emitter<HTMLCanvasElement>());\n  public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;\n  private readonly _onRemoveTextureAtlasCanvas = this._register(new Emitter<HTMLCanvasElement>());\n  public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event;\n  private readonly _onRequestRedraw = this._register(new Emitter<IRequestRedrawEvent>());\n  public readonly onRequestRedraw = this._onRequestRedraw.event;\n  private readonly _onContextLoss = this._register(new Emitter<void>());\n  public readonly onContextLoss = this._onContextLoss.event;\n\n  constructor(\n    private _terminal: Terminal,\n    private readonly _characterJoinerService: ICharacterJoinerService,\n    private readonly _charSizeService: ICharSizeService,\n    private readonly _coreBrowserService: ICoreBrowserService,\n    private readonly _coreService: ICoreService,\n    private readonly _decorationService: IDecorationService,\n    private readonly _optionsService: IOptionsService,\n    private readonly _themeService: IThemeService,\n    private readonly _customGlyphs: boolean = true,\n    preserveDrawingBuffer?: boolean\n  ) {\n    super();\n\n    // IMPORTANT: Canvas initialization and fetching of the context must be first in order to\n    // prevent possible listeners leaking and continuing to operate after the WebglRenderer has been\n    // discarded.\n    this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');\n    const contextAttributes = {\n      antialias: false,\n      depth: false,\n      preserveDrawingBuffer\n    };\n    this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;\n    if (!this._gl) {\n      throw new Error('WebGL2 not supported ' + this._gl);\n    }\n\n    this._register(this._themeService.onChangeColors(() => this._handleColorChange()));\n\n    this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService);\n\n    this._core = (this._terminal as any)._core;\n\n    this._renderLayers = [\n      new LinkRenderLayer(this._core.screenElement!, 2, this._terminal, this._core.linkifier!, this._coreBrowserService, _optionsService, this._themeService)\n    ];\n    this.dimensions = createRenderDimensions();\n    this._devicePixelRatio = this._coreBrowserService.dpr;\n    this._updateDimensions();\n    this._updateCursorBlink();\n    this._register(_optionsService.onOptionChange(() => this._handleOptionsChanged()));\n    this._textBlinkStateManager = this._register(new TextBlinkStateManager(\n      () => this._requestRedrawViewport(),\n      this._coreBrowserService,\n      this._optionsService\n    ));\n    this._resetBlinkingRowState();\n\n    this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);\n\n    this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {\n      console.log('webglcontextlost event received');\n      // Prevent the default behavior in order to enable WebGL context restoration.\n      e.preventDefault();\n      // Wait a few seconds to see if the 'webglcontextrestored' event is fired.\n      // If not, dispatch the onContextLoss notification to observers.\n      this._contextRestorationTimeout = setTimeout(() => {\n        this._contextRestorationTimeout = undefined;\n        console.warn('webgl context not restored; firing onContextLoss');\n        this._onContextLoss.fire(e);\n      }, 3000 /* ms */);\n    }));\n    this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {\n      console.warn('webglcontextrestored event received');\n      clearTimeout(this._contextRestorationTimeout);\n      this._contextRestorationTimeout = undefined;\n      // The texture atlas and glyph renderer must be fully reinitialized\n      // because their contents have been lost.\n      removeTerminalFromCache(this._terminal);\n      this._initializeWebGLState();\n      this._requestRedrawViewport();\n    }));\n\n    this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h));\n    this._register(this._coreBrowserService.onWindowChange(w => {\n      this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));\n    }));\n\n    this._register(addDisposableListener(this._coreBrowserService.mainDocument, 'mousedown', () => this._cursorBlinkStateManager.value?.restartBlinkAnimation()));\n\n    this._core.screenElement!.appendChild(this._canvas);\n\n    [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState();\n\n    this._isAttached = this._core.screenElement!.isConnected;\n\n    this._register(toDisposable(() => {\n      for (const l of this._renderLayers) {\n        l.dispose();\n      }\n      this._canvas.parentElement?.removeChild(this._canvas);\n      removeTerminalFromCache(this._terminal);\n    }));\n  }\n\n  public get textureAtlas(): HTMLCanvasElement | undefined {\n    return this._charAtlas?.pages[0].canvas;\n  }\n\n  private _handleColorChange(): void {\n    this._refreshCharAtlas();\n\n    // Force a full refresh\n    this._clearModel(true);\n  }\n\n  public handleDevicePixelRatioChange(): void {\n    // If the device pixel ratio changed, the char atlas needs to be regenerated\n    // and the terminal needs to refreshed\n    if (this._devicePixelRatio !== this._coreBrowserService.dpr) {\n      this._devicePixelRatio = this._coreBrowserService.dpr;\n      this.handleResize(this._terminal.cols, this._terminal.rows);\n    }\n  }\n\n  public handleResize(cols: number, rows: number): void {\n    // Update character and canvas dimensions\n    this._updateDimensions();\n\n    this._model.resize(this._terminal.cols, this._terminal.rows);\n    this._resetBlinkingRowState();\n\n    // Resize all render layers\n    for (const l of this._renderLayers) {\n      l.resize(this._terminal, this.dimensions);\n    }\n\n    // Resize the canvas\n    this._canvas.width = this.dimensions.device.canvas.width;\n    this._canvas.height = this.dimensions.device.canvas.height;\n    this._canvas.style.width = `${this.dimensions.css.canvas.width}px`;\n    this._canvas.style.height = `${this.dimensions.css.canvas.height}px`;\n\n    // Resize the screen\n    this._core.screenElement!.style.width = `${this.dimensions.css.canvas.width}px`;\n    this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`;\n\n    this._rectangleRenderer.value?.setDimensions(this.dimensions);\n    this._rectangleRenderer.value?.handleResize();\n    this._glyphRenderer.value?.setDimensions(this.dimensions);\n    this._glyphRenderer.value?.handleResize();\n\n    this._refreshCharAtlas();\n\n    // Force a full refresh. Resizing `_glyphRenderer` should clear it already,\n    // so there is no need to clear it again here.\n    this._clearModel(false);\n\n    // Render synchronously to avoid flicker when the canvas is cleared\n    this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });\n  }\n\n  public handleCharSizeChanged(): void {\n    this.handleResize(this._terminal.cols, this._terminal.rows);\n  }\n\n  public handleBlur(): void {\n    for (const l of this._renderLayers) {\n      l.handleBlur(this._terminal);\n    }\n    this._cursorBlinkStateManager.value?.pause();\n    // Request a redraw for active/inactive selection background\n    this._requestRedrawViewport();\n  }\n\n  public handleFocus(): void {\n    for (const l of this._renderLayers) {\n      l.handleFocus(this._terminal);\n    }\n    this._cursorBlinkStateManager.value?.resume();\n    // Request a redraw for active/inactive selection background\n    this._requestRedrawViewport();\n  }\n\n  public handleViewportVisibilityChange(isVisible: boolean): void {\n    this._textBlinkStateManager.setViewportVisible(isVisible);\n  }\n\n  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {\n    for (const l of this._renderLayers) {\n      l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);\n    }\n    this._model.selection.update(this._core, start, end, columnSelectMode);\n    this._requestRedrawViewport();\n  }\n\n  public handleCursorMove(): void {\n    for (const l of this._renderLayers) {\n      l.handleCursorMove(this._terminal);\n    }\n    this._cursorBlinkStateManager.value?.restartBlinkAnimation();\n  }\n\n  private _handleOptionsChanged(): void {\n    this._updateDimensions();\n    this._refreshCharAtlas();\n    this._updateCursorBlink();\n  }\n\n  /**\n   * Initializes members dependent on WebGL context state.\n   */\n  private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {\n    this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);\n    this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService);\n\n    // Update dimensions and acquire char atlas\n    this.handleCharSizeChanged();\n\n    return [this._rectangleRenderer.value, this._glyphRenderer.value];\n  }\n\n  /**\n   * Refreshes the char atlas, aquiring a new one if necessary.\n   */\n  private _refreshCharAtlas(): void {\n    if (this.dimensions.device.char.width <= 0 && this.dimensions.device.char.height <= 0) {\n      // Mark as not attached so char atlas gets refreshed on next render\n      this._isAttached = false;\n      return;\n    }\n\n    const atlas = acquireTextureAtlas(\n      this._terminal,\n      this._optionsService.rawOptions,\n      this._themeService.colors,\n      this.dimensions.device.cell.width,\n      this.dimensions.device.cell.height,\n      this.dimensions.device.char.width,\n      this.dimensions.device.char.height,\n      this._coreBrowserService.dpr,\n      this._deviceMaxTextureSize,\n      this._customGlyphs\n    );\n    if (this._charAtlas !== atlas) {\n      this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);\n      this._charAtlasDisposable.value = combinedDisposable(\n        EventUtils.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),\n        EventUtils.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)\n      );\n    }\n    this._charAtlas = atlas;\n    this._charAtlas.warmUp();\n    this._glyphRenderer.value?.setAtlas(this._charAtlas);\n  }\n\n  /**\n   * Clear the model.\n   * @param clearGlyphRenderer Whether to also clear the glyph renderer. This\n   * should be true generally to make sure it is in the same state as the model.\n   */\n  private _clearModel(clearGlyphRenderer: boolean): void {\n    this._model.clear();\n    if (clearGlyphRenderer) {\n      this._glyphRenderer.value?.clear();\n    }\n  }\n\n  public clearTextureAtlas(): void {\n    this._charAtlas?.clearTexture();\n    this._clearModel(true);\n    this._requestRedrawViewport();\n  }\n\n  public clear(): void {\n    this._clearModel(true);\n    for (const l of this._renderLayers) {\n      l.reset(this._terminal);\n    }\n\n    this._resetBlinkingRowState();\n    this._textBlinkStateManager.setNeedsBlinkInViewport(false);\n\n    this._cursorBlinkStateManager.value?.restartBlinkAnimation();\n    this._updateCursorBlink();\n  }\n\n  public renderRows(start: number, end: number): void {\n    if (!this._isAttached) {\n      if (this._core.screenElement?.isConnected && this._charSizeService.width && this._charSizeService.height) {\n        this._updateDimensions();\n        this._refreshCharAtlas();\n        this._isAttached = true;\n      } else {\n        return;\n      }\n    }\n\n    // Update render layers\n    for (const l of this._renderLayers) {\n      l.handleGridChanged(this._terminal, start, end);\n    }\n\n    if (!this._glyphRenderer.value || !this._rectangleRenderer.value) {\n      return;\n    }\n\n    // Tell renderer the frame is beginning\n    // upon a model clear also refresh the full viewport model\n    // (also triggered by an atlas page merge, part of #4480)\n    if (this._glyphRenderer.value.beginFrame()) {\n      this._clearModel(true);\n      this._updateModel(0, this._terminal.rows - 1);\n    } else {\n      // just update changed lines to draw\n      this._updateModel(start, end);\n    }\n\n    // Render\n    this._rectangleRenderer.value.renderBackgrounds();\n    this._glyphRenderer.value.render(this._model);\n    if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible) {\n      this._rectangleRenderer.value.renderCursor();\n    }\n  }\n\n  private _updateCursorBlink(): void {\n    if (this._coreService.decPrivateModes.cursorBlink ?? this._terminal.options.cursorBlink) {\n      this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => {\n        this._requestRedrawCursor();\n      }, this._coreBrowserService);\n    } else {\n      this._cursorBlinkStateManager.clear();\n    }\n    // Request a refresh from the terminal as management of rendering is being\n    // moved back to the terminal\n    this._requestRedrawCursor();\n  }\n\n  private _updateModel(start: number, end: number): void {\n    const terminal = this._core;\n    let cell: ICellData = this._workCell;\n\n    // Declare variable ahead of time to avoid garbage collection\n    let lastBg: number;\n    let y: number;\n    let row: number;\n    let line: IBufferLine;\n    let joinedRanges: [number, number][];\n    let isJoined: boolean;\n    let skipJoinedCheckUntilX: number = 0;\n    let isValidJoinRange: boolean = true;\n    let lastCharX: number;\n    let range: [number, number];\n    let isCursorRow: boolean;\n    let chars: string;\n    let code: number;\n    let width: number;\n    let i: number;\n    let x: number;\n    let j: number;\n    start = clamp(start, terminal.rows - 1, 0);\n    end = clamp(end, terminal.rows - 1, 0);\n    const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? terminal.options.cursorStyle ?? 'block';\n\n    const cursorY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY;\n    const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp;\n    // in case cursor.x == cols adjust visual cursor to cols - 1\n    const cursorX = Math.min(this._terminal.buffer.active.cursorX, terminal.cols - 1);\n    let lastCursorX = -1;\n    const isCursorVisible =\n      this._coreService.isCursorInitialized &&\n      !this._coreService.isCursorHidden &&\n      (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible);\n    this._model.cursor = undefined;\n    let modelUpdated = false;\n\n    for (y = start; y <= end; y++) {\n      row = y + terminal.buffer.ydisp;\n      line = terminal.buffer.lines.get(row)!;\n      let rowHasBlinkingCells = false;\n      this._model.lineLengths[y] = 0;\n      isCursorRow = cursorY === row;\n      skipJoinedCheckUntilX = 0;\n      joinedRanges = this._characterJoinerService.getJoinedCharacters(row);\n      for (x = 0; x < terminal.cols; x++) {\n        lastBg = this._cellColorResolver.result.bg;\n        line.loadCell(x, cell);\n\n        if (x === 0) {\n          lastBg = this._cellColorResolver.result.bg;\n        }\n\n        // If true, indicates that the current character(s) to draw were joined.\n        isJoined = false;\n\n        // Indicates whether this cell is part of a joined range that should be ignored as it cannot\n        // be rendered entirely, like the selection state differs across the range.\n        isValidJoinRange = (x >= skipJoinedCheckUntilX);\n\n        lastCharX = x;\n\n        // Process any joined character ranges as needed. Because of how the\n        // ranges are produced, we know that they are valid for the characters\n        // and attributes of our input.\n        if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {\n          range = joinedRanges.shift()!;\n\n          // If the ligature's selection state is not consistent, don't join it. This helps the\n          // selection render correctly regardless whether they should be joined.\n          const firstSelectionState = this._model.selection.isCellSelected(this._terminal, range[0], row);\n          for (i = range[0] + 1; i < range[1]; i++) {\n            isValidJoinRange &&= (firstSelectionState === this._model.selection.isCellSelected(this._terminal, i, row));\n          }\n          // Similarly, if the cursor is in the ligature, don't join it.\n          isValidJoinRange &&= !isCursorRow || cursorX < range[0] || cursorX >= range[1];\n          if (!isValidJoinRange) {\n            skipJoinedCheckUntilX = range[1];\n          } else {\n            isJoined = true;\n\n            // We already know the exact start and end column of the joined range,\n            // so we get the string and width representing it directly.\n            cell = new JoinedCellData(\n              cell,\n              line!.translateToString(true, range[0], range[1]),\n              range[1] - range[0]\n            );\n\n            // Skip over the cells occupied by this range in the loop\n            lastCharX = range[1] - 1;\n          }\n        }\n\n        chars = cell.getChars();\n        code = cell.getCode();\n        i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;\n\n        if (!rowHasBlinkingCells && cell.isBlink()) {\n          rowHasBlinkingCells = true;\n        }\n\n        // Load colors/resolve overrides into work colors\n        this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);\n\n        // Override colors for cursor cell\n        if (isCursorVisible && row === cursorY) {\n          if (x === cursorX) {\n            this._model.cursor = {\n              x: cursorX,\n              y: viewportRelativeCursorY,\n              width: cell.getWidth(),\n              style: this._coreBrowserService.isFocused ? cursorStyle : terminal.options.cursorInactiveStyle,\n              cursorWidth: terminal.options.cursorWidth,\n              dpr: this._devicePixelRatio\n            };\n            lastCursorX = cursorX + cell.getWidth() - 1;\n          }\n          if (x >= cursorX && x <= lastCursorX &&\n              ((this._coreBrowserService.isFocused &&\n              cursorStyle === 'block') ||\n              (this._coreBrowserService.isFocused === false &&\n              terminal.options.cursorInactiveStyle === 'block'))\n          ) {\n            this._cellColorResolver.result.fg =\n              Attributes.CM_RGB | (this._themeService.colors.cursorAccent.rgba >> 8 & Attributes.RGB_MASK);\n            this._cellColorResolver.result.bg =\n              Attributes.CM_RGB | (this._themeService.colors.cursor.rgba >> 8 & Attributes.RGB_MASK);\n          }\n        }\n\n        if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {\n          this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;\n        }\n\n        if (code !== NULL_CELL_CODE) {\n          this._model.lineLengths[y] = x + 1;\n        }\n\n        // Nothing has changed, no updates needed\n        if (this._model.cells[i] === code &&\n            this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg &&\n            this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg &&\n            this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) {\n          continue;\n        }\n\n        modelUpdated = true;\n\n        // Flag combined chars with a bit mask so they're easily identifiable\n        if (chars.length > 1) {\n          code |= COMBINED_CHAR_BIT_MASK;\n        }\n\n        // Cache the results in the model\n        this._model.cells[i] = code;\n        this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;\n        this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;\n        this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;\n\n        width = cell.getWidth();\n        this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg);\n\n        if (isJoined) {\n          // Restore work cell\n          cell = this._workCell;\n\n          // Null out non-first cells\n          for (x++; x <= lastCharX; x++) {\n            j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;\n            this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);\n            this._model.cells[j] = NULL_CELL_CODE;\n            // Don't re-resolve the cell color since multi-colored ligature backgrounds are not\n            // supported\n            this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;\n            this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;\n            this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;\n          }\n          x--; // Go back to the previous update cell for next iteration\n        }\n      }\n      this._setRowBlinkState(y, rowHasBlinkingCells);\n    }\n    if (modelUpdated) {\n      this._rectangleRenderer.value!.updateBackgrounds(this._model);\n    }\n    this._rectangleRenderer.value!.updateCursor(this._model);\n    this._updateTextBlinkState();\n  }\n\n  private _resetBlinkingRowState(): void {\n    this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);\n    this._rowHasBlinkingCellsCount = 0;\n  }\n\n  private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {\n    const previous = this._rowHasBlinkingCells[row];\n    if (previous === hasBlinkingCells) {\n      return;\n    }\n    this._rowHasBlinkingCells[row] = hasBlinkingCells;\n    this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;\n  }\n\n  private _updateTextBlinkState(): void {\n    this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);\n  }\n\n  /**\n   * Recalculates the character and canvas dimensions.\n   */\n  private _updateDimensions(): void {\n    // Perform a new measure if the CharMeasure dimensions are not yet available\n    if (!this._charSizeService.width || !this._charSizeService.height) {\n      return;\n    }\n\n    // Calculate the device character width. Width is floored as it must be drawn to an integer grid\n    // in order for the char atlas glyphs to not be blurry.\n    this.dimensions.device.char.width = Math.floor(this._charSizeService.width * this._devicePixelRatio);\n\n    // Calculate the device character height. Height is ceiled in case devicePixelRatio is a\n    // floating point number in order to ensure there is enough space to draw the character to the\n    // cell.\n    this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * this._devicePixelRatio);\n\n    // Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be\n    // floored since lineHeight can never be lower then 1, this guarentees the device cell height\n    // will always be larger than device char height.\n    this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);\n\n    // Calculate the y offset within a cell that glyph should draw at in order for it to be centered\n    // correctly within the cell.\n    this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);\n\n    // Calculate the device cell width, taking the letterSpacing into account.\n    this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);\n\n    // Calculate the x offset with a cell that text should draw from in order for it to be centered\n    // correctly within the cell.\n    this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2);\n\n    // Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in\n    // the canvas\n    this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;\n    this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;\n\n    // The the size of the canvas on the page. It's important that this rounds to nearest integer\n    // and not ceils as browsers often have floating point precision issues where\n    // `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when\n    // it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too\n    // large for the canvas element size.\n    this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);\n    this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);\n\n    // Get the CSS dimensions of an individual cell. This needs to be derived from the calculated\n    // device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the\n    // page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char\n    // size on the canvas can differ.\n    this.dimensions.css.cell.height = this.dimensions.device.cell.height / this._devicePixelRatio;\n    this.dimensions.css.cell.width = this.dimensions.device.cell.width / this._devicePixelRatio;\n  }\n\n  private _setCanvasDevicePixelDimensions(width: number, height: number): void {\n    if (this._canvas.width === width && this._canvas.height === height) {\n      return;\n    }\n    // While the actual canvas size has changed, keep device canvas dimensions as the value before\n    // the change as it's an exact multiple of the cell sizes.\n    this._canvas.width = width;\n    this._canvas.height = height;\n    // Render synchronously to avoid flicker when the canvas is cleared\n    this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });\n  }\n\n  private _requestRedrawViewport(): void {\n    this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });\n  }\n\n  private _requestRedrawCursor(): void {\n    const cursorY = this._terminal.buffer.active.cursorY;\n    this._onRequestRedraw.fire({ start: cursorY, end: cursorY });\n  }\n}\n\n// TODO: Share impl with core\nexport class JoinedCellData extends AttributeData implements ICellData {\n  private _width: number;\n  // .content carries no meaning for joined CellData, simply nullify it\n  // thus we have to overload all other .content accessors\n  public content: number = 0;\n  public fg: number;\n  public bg: number;\n  public combinedData: string = '';\n\n  constructor(firstCell: ICellData, chars: string, width: number) {\n    super();\n    this.fg = firstCell.fg;\n    this.bg = firstCell.bg;\n    this.combinedData = chars;\n    this._width = width;\n  }\n\n  public isCombined(): number {\n    // always mark joined cell data as combined\n    return Content.IS_COMBINED_MASK;\n  }\n\n  public getWidth(): number {\n    return this._width;\n  }\n\n  public getChars(): string {\n    return this.combinedData;\n  }\n\n  public getCode(): number {\n    // code always gets the highest possible fake codepoint (read as -1)\n    // this is needed as code is used by caches as identifier\n    return 0x1FFFFF;\n  }\n\n  public setFromCharData(value: CharData): void {\n    throw new Error('not implemented');\n  }\n\n  public getAsCharData(): CharData {\n    return [this.fg, this.getChars(), this.getWidth(), this.getCode()];\n  }\n}\n\nfunction clamp(value: number, max: number, min: number = 0): number {\n  return Math.max(Math.min(value, max), min);\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/WebglUtils.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\n\n/**\n * A matrix that when multiplies will translate 0-1 coordinates (left to right,\n * top to bottom) to clip space.\n */\nexport const PROJECTION_MATRIX = new Float32Array([\n  2, 0, 0, 0,\n  0, -2, 0, 0,\n  0, 0, 1, 0,\n  -1, 1, 0, 1\n]);\n\nexport function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined {\n  const program = throwIfFalsy(gl.createProgram());\n  gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));\n  gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));\n  gl.linkProgram(program);\n  const success = gl.getProgramParameter(program, gl.LINK_STATUS);\n  if (success) {\n    return program;\n  }\n\n  console.error(gl.getProgramInfoLog(program));\n  gl.deleteProgram(program);\n}\n\nexport function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {\n  const shader = throwIfFalsy(gl.createShader(type));\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);\n  if (success) {\n    return shader;\n  }\n\n  console.error(gl.getShaderInfoLog(shader));\n  gl.deleteShader(shader);\n}\n\nexport function expandFloat32Array(source: Float32Array, max: number): Float32Array {\n  const newLength = Math.min(source.length * 2, max);\n  const newArray = new Float32Array(newLength);\n  for (let i = 0; i < source.length; i++) {\n    newArray[i] = source[i];\n  }\n  return newArray;\n}\n\nexport class GLTexture {\n  public texture: WebGLTexture;\n  public version: number;\n\n  constructor(texture: WebGLTexture) {\n    this.texture = texture;\n    this.version = -1;\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/customGlyphs/CustomGlyphDefinitions.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition } from './Types';\n\n/* eslint-disable max-len */\n\nconst enum Shapes {\n  /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',\n  /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',\n\n  /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',\n  /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',\n  /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',\n  /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',\n\n  /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',\n  /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',\n  /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',\n  /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',\n\n  /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',\n  /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',\n  /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',\n  /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',\n\n  /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',\n\n  /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled\n  /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled\n  /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled\n  /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',\n  /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',\n  /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',\n}\n\nnamespace GitBranchSymbolsParts {\n  // Lines\n  export const LINE_H: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 });\n  export const LINE_V: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 1 });\n\n  // Fading lines\n  export const FADE_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5', strokeWidth: 1 });\n  export const FADE_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5', strokeWidth: 1 });\n  export const FADE_DOWN: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90', strokeWidth: 1 });\n  export const FADE_UP: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1', strokeWidth: 1 });\n\n  // Curved corners\n  export const CURVE_DOWN_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,1,.5`, strokeWidth: 1 });\n  export const CURVE_DOWN_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,0,.5`, strokeWidth: 1 });\n  export const CURVE_UP_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,1,.5`, strokeWidth: 1 });\n  export const CURVE_UP_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,0,.5`, strokeWidth: 1 });\n\n  // Node parts\n  export const NODE_FILL: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5' });\n  export const NODE_STROKE: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5', strokeWidth: 1 });\n  export const NODE_LINE_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M1,.5 L.85,.5', strokeWidth: 1 });\n  export const NODE_LINE_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M0,.5 L.15,.5', strokeWidth: 1 });\n  export const NODE_LINE_DOWN: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.5,1 L.5,.7', strokeWidth: 1 });\n  export const NODE_LINE_UP: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.5,0 L.5,.3', strokeWidth: 1 });\n}\n\nexport const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefinition | undefined } = {\n  // #region Box Drawing (2500-257F)\n\n  // https://www.unicode.org/charts/PDF/U2500.pdf\n\n  // Light and heavy solid lines (2500-2503)\n  '─': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 },\n  '━': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 3 },\n  '│': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 1 },\n  '┃': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 3 },\n\n  // Light and heavy dashed lines (2504-250B)\n  '┄': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.THREE_DASHES_HORIZONTAL, strokeWidth: 1 },\n  '┅': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.THREE_DASHES_HORIZONTAL, strokeWidth: 3 },\n  '┆': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.THREE_DASHES_VERTICAL, strokeWidth: 1 },\n  '┇': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.THREE_DASHES_VERTICAL, strokeWidth: 3 },\n  '┈': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.FOUR_DASHES_HORIZONTAL, strokeWidth: 1 },\n  '┉': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.FOUR_DASHES_HORIZONTAL, strokeWidth: 3 },\n  '┊': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.FOUR_DASHES_VERTICAL, strokeWidth: 1 },\n  '┋': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.FOUR_DASHES_VERTICAL, strokeWidth: 3 },\n\n  // Light and heavy line box components (250C-254B)\n  '┌': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 1 },\n  '┍': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '┎': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '┏': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 3 },\n  '┐': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 1 },\n  '┑': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '┒': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '┓': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 3 },\n  '└': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 1 },\n  '┕': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '┖': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n  '┗': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 3 },\n  '┘': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 1 },\n  '┙': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '┚': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n  '┛': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 3 },\n  '├': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_RIGHT, strokeWidth: 1 },\n  '┝': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '┞': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n  '┟': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '┠': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 3 }],\n  '┡': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 3 }],\n  '┢': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 3 }],\n  '┣': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_RIGHT, strokeWidth: 3 },\n  '┤': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_LEFT, strokeWidth: 1 },\n  '┥': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '┦': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n  '┧': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '┨': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 3 }],\n  '┩': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 3 }],\n  '┪': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 3 }],\n  '┫': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_LEFT, strokeWidth: 3 },\n  '┬': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_BOTTOM, strokeWidth: 1 },\n  '┭': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '┮': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '┯': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 3 }],\n  '┰': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '┱': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 3 }],\n  '┲': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 3 }],\n  '┳': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_BOTTOM, strokeWidth: 3 },\n  '┴': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_TOP, strokeWidth: 1 },\n  '┵': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '┶': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '┷': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 3 }],\n  '┸': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n  '┹': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 3 }],\n  '┺': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 3 }],\n  '┻': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.T_TOP, strokeWidth: 3 },\n  '┼': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.CROSS, strokeWidth: 1 },\n  '┽': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '┾': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '┿': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 3 }],\n  '╀': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n  '╁': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '╂': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 3 }],\n  '╃': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 3 }],\n  '╄': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 3 }],\n  '╅': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_BOTTOM, strokeWidth: 3 }],\n  '╆': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.RIGHT_TO_BOTTOM, strokeWidth: 3 }],\n  '╇': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, strokeWidth: 3 }],\n  '╈': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, strokeWidth: 3 }],\n  '╉': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, strokeWidth: 3 }],\n  '╊': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, strokeWidth: 3 }],\n  '╋': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.CROSS, strokeWidth: 3 },\n\n  // Light and heavy dashed lines (254C-254F)\n  '╌': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TWO_DASHES_HORIZONTAL, strokeWidth: 1 },\n  '╍': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TWO_DASHES_HORIZONTAL, strokeWidth: 3 },\n  '╎': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TWO_DASHES_VERTICAL, strokeWidth: 1 },\n  '╏': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TWO_DASHES_VERTICAL, strokeWidth: 3 },\n\n  // Double lines (2550-2551)\n  '═': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}`, strokeWidth: 1 },\n  '║': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1`, strokeWidth: 1 },\n\n  // Light and double line box components (2552-256C)\n  '╒': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}`, strokeWidth: 1 },\n  '╓': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1`, strokeWidth: 1 },\n  '╔': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1`, strokeWidth: 1 },\n  '╕': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}`, strokeWidth: 1 },\n  '╖': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1`, strokeWidth: 1 },\n  '╗': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1`, strokeWidth: 1 },\n  '╘': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}`, strokeWidth: 1 },\n  '╙': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0`, strokeWidth: 1 },\n  '╚': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0`, strokeWidth: 1 },\n  '╛': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}`, strokeWidth: 1 },\n  '╜': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0`, strokeWidth: 1 },\n  '╝': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0`, strokeWidth: 1 },\n  '╞': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}`, strokeWidth: 1 },\n  '╟': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5`, strokeWidth: 1 },\n  '╠': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0`, strokeWidth: 1 },\n  '╡': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}`, strokeWidth: 1 },\n  '╢': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1`, strokeWidth: 1 },\n  '╣': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0`, strokeWidth: 1 },\n  '╤': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1`, strokeWidth: 1 },\n  '╥': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1`, strokeWidth: 1 },\n  '╦': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1`, strokeWidth: 1 },\n  '╧': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}`, strokeWidth: 1 },\n  '╨': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0`, strokeWidth: 1 },\n  '╩': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0`, strokeWidth: 1 },\n  '╪': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}`, strokeWidth: 1 },\n  '╫': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1`, strokeWidth: 1 },\n  '╬': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0`, strokeWidth: 1 },\n\n  // Character cell arcs (256D-2570)\n  '╭': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,1,.5`, strokeWidth: 1 },\n  '╮': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,0,.5`, strokeWidth: 1 },\n  '╯': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,0,.5`, strokeWidth: 1 },\n  '╰': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,1,.5`, strokeWidth: 1 },\n\n  // Character cell diagonals (2571-2573)\n  '╱': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,0 L0,1', strokeWidth: 1 },\n  '╲': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L1,1', strokeWidth: 1 },\n  '╳': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,0 L0,1 M0,0 L1,1', strokeWidth: 1 },\n\n  // Light and heavy half lines (2574-257B)\n  '╴': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 },\n  '╵': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 },\n  '╶': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 },\n  '╷': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 },\n  '╸': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 },\n  '╹': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 },\n  '╺': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 },\n  '╻': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 },\n\n  // Mixed light and heavy lines (257C-257F)\n  '╼': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 3 }],\n  '╽': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 3 }],\n  '╾': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_RIGHT, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_LEFT, strokeWidth: 3 }],\n  '╿': [{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_BOTTOM, strokeWidth: 1 }, { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.MIDDLE_TO_TOP, strokeWidth: 3 }],\n\n  // #endregion\n\n  // #region Block elements (2580-259F)\n\n  // https://www.unicode.org/charts/PDF/U2580.pdf\n\n  // Block elements (2580-2590)\n  '▀': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 4 }] }, // UPPER HALF BLOCK\n  '▁': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 7, w: 8, h: 1 }] }, // LOWER ONE EIGHTH BLOCK\n  '▂': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 6, w: 8, h: 2 }] }, // LOWER ONE QUARTER BLOCK\n  '▃': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 5, w: 8, h: 3 }] }, // LOWER THREE EIGHTHS BLOCK\n  '▄': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 4, w: 8, h: 4 }] }, // LOWER HALF BLOCK\n  '▅': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 3, w: 8, h: 5 }] }, // LOWER FIVE EIGHTHS BLOCK\n  '▆': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 2, w: 8, h: 6 }] }, // LOWER THREE QUARTERS BLOCK\n  '▇': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 1, w: 8, h: 7 }] }, // LOWER SEVEN EIGHTHS BLOCK\n  '█': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 8 }] }, // FULL BLOCK (=solid -> 25A0=black square)\n  '▉': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 7, h: 8 }] }, // LEFT SEVEN EIGHTHS BLOCK\n  '▊': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 6, h: 8 }] }, // LEFT THREE QUARTERS BLOCK\n  '▋': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 5, h: 8 }] }, // LEFT FIVE EIGHTHS BLOCK\n  '▌': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 4, h: 8 }] }, // LEFT HALF BLOCK\n  '▍': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 3, h: 8 }] }, // LEFT THREE EIGHTHS BLOCK\n  '▎': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 2, h: 8 }] }, // LEFT ONE QUARTER BLOCK\n  '▏': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 1, h: 8 }] }, // LEFT ONE EIGHTH BLOCK\n  '▐': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 4, h: 8 }] }, // RIGHT HALF BLOCK\n\n  // Shade characters (2591-2593)\n  '░': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // LIGHT SHADE (25%)\n    [1, 0],\n    [0, 0]\n  ] },\n  '▒': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // MEDIUM SHADE (=speckles fill, dotted fill, 50%, used in mapping to cp949, -> 1FB90 inverse medium shade)\n    [1, 0],\n    [0, 1]\n  ] },\n  '▓': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // DARK SHADE (75%)\n    [1, 1],\n    [1, 0]\n  ] },\n\n  // Block elements (2594-2595)\n  '▔': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 1 }] }, // UPPER ONE EIGHTH BLOCK\n  '▕': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 7, y: 0, w: 1, h: 8 }] }, // RIGHT ONE EIGHTH BLOCK\n\n  // Terminal graphic characters (2596-259F)\n  '▖': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 4, w: 4, h: 4 }] },                             // QUADRANT LOWER LEFT\n  '▗': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 4, w: 4, h: 4 }] },                             // QUADRANT LOWER RIGHT\n  '▘': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 4, h: 4 }] },                             // QUADRANT UPPER LEFT\n  '▙': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }] }, // QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT (-> 1F67F reverse checker board, -> 1FB95 checker board fill)\n  '▚': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }] }, // QUADRANT UPPER LEFT AND LOWER RIGHT\n  '▛': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 4, h: 8 }, { x: 4, y: 0, w: 4, h: 4 }] }, // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT\n  '▜': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }] }, // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT\n  '▝': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 4, h: 4 }] },                             // QUADRANT UPPER RIGHT\n  '▞': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }] }, // QUADRANT UPPER RIGHT AND LOWER LEFT (-> 1F67E checker board, 1FB96 inverse checker board fill)\n  '▟': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }] }, // QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT\n\n  // #endregion\n\n  // #region Powerline Symbols (E0A0-E0BF)\n\n  // This contains the definitions of the primarily used box drawing characters as vector shapes.\n  // The reason these characters are defined specially is to avoid common problems if a user's font\n  // has not been patched with powerline characters and also to get pixel perfect rendering as\n  // rendering issues can occur around AA/SPAA.\n  //\n  // The line variants draw beyond the cell and get clipped to ensure the end of the line is not\n  // visible.\n  //\n  // Original symbols defined in https://github.com/powerline/fontpatcher\n\n  // Git branch\n  '\\u{E0A0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655', type: CustomGlyphVectorType.FILL } },\n  // LN (Line Number)\n  '\\u{E0A1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5', type: CustomGlyphVectorType.FILL } },\n  // Lock\n  '\\u{E0A2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82', type: CustomGlyphVectorType.FILL } },\n  // CN (Column Number)\n  '\\u{E0A3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5', type: CustomGlyphVectorType.FILL } },\n  // Right triangle solid\n  '\\u{E0B0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,.5 L0,1', type: CustomGlyphVectorType.FILL, rightPadding: 2 } },\n  // Right triangle line\n  '\\u{E0B1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M-1,-.5 L1,.5 L-1,1.5', type: CustomGlyphVectorType.STROKE, leftPadding: 1, rightPadding: 1 } },\n  // Left triangle solid\n  '\\u{E0B2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 L0,.5 L1,1', type: CustomGlyphVectorType.FILL, leftPadding: 2 } },\n  // Left triangle line\n  '\\u{E0B3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M2,-.5 L0,.5 L2,1.5', type: CustomGlyphVectorType.STROKE, leftPadding: 1, rightPadding: 1 } },\n\n  // Powerline Extra Symbols\n\n  // Original symbols defined in https://github.com/ryanoasis/powerline-extra-symbols\n\n  // Right semi-circle solid\n  '\\u{E0B4}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: CustomGlyphVectorType.FILL, rightPadding: 1 } },\n  // Right semi-circle line\n  '\\u{E0B5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0', type: CustomGlyphVectorType.STROKE, rightPadding: 1 } },\n  // Left semi-circle solid\n  '\\u{E0B6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: CustomGlyphVectorType.FILL, leftPadding: 1 } },\n  // Left semi-circle line\n  '\\u{E0B7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0', type: CustomGlyphVectorType.STROKE, leftPadding: 1 } },\n  // Lower left triangle\n  '\\u{E0B8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: CustomGlyphVectorType.FILL } },\n  // Backslash separator\n  '\\u{E0B9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M-.5,-.5 L1.5,1.5', type: CustomGlyphVectorType.STROKE, leftPadding: 1, rightPadding: 1 } },\n  // Lower right triangle\n  '\\u{E0BA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1.5,-.5 L-.5,1.5 L1.5,1.5', type: CustomGlyphVectorType.FILL } },\n  // Forward slash separator redundant (identical to E0BD)\n  '\\u{E0BB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1.5,-.5 L-.5,1.5', type: CustomGlyphVectorType.STROKE, leftPadding: 1, rightPadding: 1 } },\n  // Upper left triangle\n  '\\u{E0BC}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1.5,-.5 L-.5,1.5 L-.5,-.5', type: CustomGlyphVectorType.FILL } },\n  // Forward slash separator\n  '\\u{E0BD}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1.5,-.5 L-.5,1.5', type: CustomGlyphVectorType.STROKE, leftPadding: 1, rightPadding: 1 } },\n  // Upper right triangle\n  '\\u{E0BE}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M-.5,-.5 L1.5,1.5 L1.5,-.5', type: CustomGlyphVectorType.FILL } },\n  // Backslash separator redundant (identical to E0B9)\n  '\\u{E0BF}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M-.5,-.5 L1.5,1.5', type: CustomGlyphVectorType.STROKE, leftPadding: 1, rightPadding: 1 } },\n  '\\u{E0C0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.8069,0.6075 Q0.7991,0.612,0.7815,0.6218 T0.7542,0.6369 T0.7282,0.6508 T0.7016,0.6644 T0.6778,0.6758 T0.6551,0.6854 T0.637,0.6911 T0.622,0.6936 Q0.6186,0.6936,0.6128,0.6942 T0.6018,0.6952 T0.5894,0.6956 T0.5757,0.6952 T0.561,0.693 T0.5451,0.6879 Q0.5342,0.6834,0.5193,0.6756 T0.4925,0.6614 T0.4673,0.6485 T0.4436,0.6389 T0.4239,0.6363 T0.4089,0.6422 Q0.4045,0.6467,0.3958,0.6554 T0.3843,0.6669 T0.3748,0.6746 T0.3613,0.683 T0.3442,0.6903 L0.3499,0.6956 Q0.3584,0.7042,0.3679,0.7177 T0.3836,0.7403 T0.4019,0.7583 T0.4295,0.7703 Q0.4688,0.7785,0.5203,0.7605 Q0.4902,0.7817,0.449,0.7911 T0.3696,0.8001 Q0.3567,0.7997,0.341,0.7942 T0.3111,0.7813 T0.2805,0.766 T0.2493,0.753 T0.218,0.7472 T0.187,0.754 Q0.1762,0.7589,0.1653,0.7681 T0.1453,0.7895 T0.1308,0.818 T0.1264,0.8507 L0.1264,0.858 Q0.1453,0.8462,0.1607,0.8433 T0.1899,0.8452 T0.2226,0.8625 Q0.2344,0.8707,0.2485,0.8752 T0.2813,0.8831 T0.3089,0.889 Q0.2981,0.8915,0.2846,0.8956 T0.2603,0.9037 T0.2368,0.9115 T0.2129,0.9174 T0.1894,0.9194 T0.1653,0.9155 Q0.1504,0.9115,0.1328,0.9196 T0.0986,0.9421 T0.0655,0.9708 T0.0317,0.9943 T0,1 Q0.001,0.94,0.0017,0.6508 T0.0024,0.2322 Q0.002,0.2109,0.0007,0.1379 T0,0.0249 Q0.0058,0.0253,0.009,0.0257 T0.0156,0.0288 T0.0203,0.0333 T0.0264,0.0422 T0.0349,0.0539 Q0.044,0.0657,0.0542,0.072 T0.0752,0.0789 T0.096,0.0775 T0.1175,0.0694 T0.1379,0.0573 T0.1579,0.0432 Q0.2141,0,0.2673,0.0318 Q0.2588,0.0318,0.2517,0.033 T0.239,0.0361 T0.2276,0.0424 T0.218,0.0504 T0.2085,0.0612 T0.1997,0.0732 T0.1899,0.0875 T0.1795,0.1028 Q0.1748,0.1093,0.1507,0.1234 T0.1186,0.1448 Q0.0999,0.1612,0.1108,0.1783 Q0.1162,0.1877,0.1238,0.1928 T0.1404,0.1979 T0.158,0.1967 T0.1775,0.1895 T0.1963,0.1791 T0.2151,0.1663 T0.2314,0.1534 T0.2458,0.141 T0.2561,0.1322 Q0.2815,0.1106,0.3017,0.101 T0.344,0.0926 T0.3919,0.1077 Q0.4099,0.1175,0.4265,0.1206 T0.4588,0.1202 T0.488,0.1098 T0.5176,0.0914 Q0.5071,0.1032,0.4963,0.1122 T0.4722,0.1285 T0.4497,0.1401 T0.4223,0.1518 T0.3943,0.1636 Q0.374,0.1726,0.3623,0.1863 T0.3482,0.2162 Q0.3445,0.2403,0.3608,0.2605 T0.4072,0.2876 Q0.4194,0.2909,0.4326,0.2899 T0.457,0.2856 T0.4805,0.2764 T0.5032,0.264 T0.5256,0.2497 T0.5478,0.2352 T0.5698,0.2222 T0.5921,0.2118 Q0.6982,0.173,0.81,0.2436 Q0.833,0.2583,0.8496,0.2674 T0.887,0.2846 T0.925,0.294 T0.9617,0.2911 T1,0.2746 Q0.9817,0.2946,0.96,0.3056 T0.9175,0.3191 T0.8731,0.3209 T0.8277,0.3164 T0.7818,0.3109 T0.7359,0.3101 T0.6907,0.3197 T0.647,0.3448 Q0.6443,0.3472,0.6333,0.3562 T0.6194,0.3674 T0.6076,0.3766 T0.594,0.386 T0.5815,0.3931 T0.5661,0.4007 T0.5501,0.4064 L0.5556,0.4121 Q0.5627,0.4198,0.5722,0.4251 T0.5926,0.4337 T0.6131,0.439 T0.6343,0.4431 T0.6521,0.4468 Q0.6762,0.4529,0.7056,0.4463 Q0.6772,0.4623,0.6543,0.471 T0.604,0.4814 Q0.5962,0.4818,0.5815,0.4825 T0.5591,0.4835 T0.5393,0.4857 T0.519,0.4908 T0.5007,0.4996 T0.4812,0.5141 T0.4627,0.5353 L0.4549,0.5451 L0.4661,0.543 Q0.4759,0.541,0.4909,0.55 T0.5315,0.5796 T0.5684,0.6083 Q0.5799,0.6161,0.5915,0.6212 T0.6181,0.6308 L0.6331,0.6353 Q0.6514,0.6418,0.7004,0.633 T0.8069,0.6075 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.011,0.0469 Q0.0152,0.0412,0.02,0.0361 T0.0292,0.0281 T0.0365,0.0253 Q0.0426,0.0257,0.0461,0.0263 T0.0534,0.03 T0.0582,0.0345 T0.0645,0.0434 T0.0723,0.0546 Q0.0807,0.0653,0.0897,0.0712 T0.1081,0.0775 T0.1255,0.0765 T0.1438,0.0695 T0.1605,0.0593 T0.1772,0.0473 Q0.2388,0,0.2927,0.0326 L0.2614,0.0767 L0.2581,0.0767 Q0.2494,0.0767,0.2444,0.0773 T0.2359,0.0789 T0.2291,0.0842 T0.2233,0.0922 T0.2146,0.1054 T0.2026,0.1236 Q0.1943,0.1354,0.1843,0.1444 T0.1675,0.1574 T0.1526,0.1654 T0.1429,0.1701 Q0.1436,0.1717,0.1446,0.1733 Q0.1504,0.1823,0.1581,0.1866 T0.1733,0.1915 T0.1909,0.1884 T0.2085,0.1809 T0.2265,0.169 T0.2427,0.1564 T0.2575,0.1436 T0.2688,0.1338 Q0.2969,0.1101,0.3195,0.0995 T0.3643,0.0901 T0.4114,0.1048 Q0.4279,0.1138,0.4426,0.1168 T0.4705,0.117 T0.4945,0.1089 T0.5186,0.0942 L0.5211,0.0926 L0.5289,0.0922 Q0.5411,0.095,0.5189,0.1203 Q0.505,0.1358,0.4913,0.1474 T0.4634,0.167 T0.4389,0.1796 T0.4121,0.1909 T0.3866,0.2011 Q0.3782,0.2047,0.3727,0.2092 Q0.3717,0.2117,0.3714,0.2141 Q0.3695,0.2263,0.3751,0.2388 T0.394,0.2616 T0.4253,0.2761 Q0.4398,0.2785,0.4513,0.2779 T0.4756,0.2724 T0.4987,0.2616 T0.5216,0.2476 T0.5453,0.2321 T0.5703,0.217 T0.5976,0.2043 Q0.7015,0.166,0.8099,0.2345 Q0.8361,0.2512,0.8572,0.2616 T0.9006,0.2779 T0.9432,0.281 T0.9819,0.2663 L0.9887,0.2671 Q1,0.272,0.9768,0.2969 Q0.9558,0.3193,0.9319,0.333 T0.8858,0.3513 T0.8391,0.3569 T0.793,0.3546 T0.7485,0.3497 T0.7065,0.3473 T0.6681,0.3522 T0.6344,0.3699 Q0.5963,0.4005,0.5847,0.4082 Q0.595,0.4139,0.6079,0.4174 T0.6367,0.4237 T0.6586,0.4278 Q0.6793,0.4331,0.7044,0.4278 Q0.7057,0.4278,0.707,0.4274 Q0.7067,0.429,0.7062,0.4313 T0.7036,0.4398 T0.6993,0.4513 T0.6931,0.4619 T0.6851,0.4698 Q0.6573,0.4857,0.6336,0.4947 T0.5831,0.5053 Q0.577,0.5057,0.5615,0.5065 T0.5394,0.508 T0.5221,0.51 T0.5042,0.5139 T0.4905,0.5204 Q0.5008,0.5232,0.515,0.5328 T0.549,0.5585 T0.5799,0.5824 Q0.5905,0.5897,0.6013,0.5946 T0.6262,0.6036 T0.6409,0.6081 Q0.6567,0.6134,0.7035,0.605 T0.8028,0.5816 L0.7974,0.5922 T0.7867,0.6134 L0.7812,0.624 Q0.7799,0.6248,0.7786,0.6252 Q0.7709,0.6297,0.7573,0.6372 T0.7346,0.6499 T0.7123,0.6621 T0.6897,0.6741 T0.6686,0.6847 T0.648,0.6941 T0.6297,0.7013 T0.6129,0.7062 T0.5995,0.7076 Q0.5992,0.7076,0.5831,0.7092 T0.5529,0.7092 T0.525,0.7019 Q0.5105,0.6958,0.4824,0.6811 T0.4356,0.6586 T0.4079,0.6533 Q0.3872,0.6741,0.3827,0.6782 Q0.3872,0.6835,0.3934,0.6929 T0.4037,0.7078 T0.4142,0.7198 T0.4284,0.7304 T0.4466,0.7365 Q0.4824,0.7443,0.5292,0.728 L0.5292,0.7294 T0.529,0.7333 T0.5284,0.7392 T0.5266,0.7463 T0.5236,0.7539 T0.5186,0.7612 T0.5111,0.7679 Q0.5086,0.77,0.5056,0.7716 Q0.4466,0.8104,0.3591,0.8091 Q0.344,0.8091,0.3246,0.802 T0.2893,0.7863 T0.2543,0.77 T0.2206,0.76 T0.1894,0.7643 Q0.1804,0.7684,0.1733,0.7753 Q0.1591,0.7892,0.1601,0.8169 Q0.1784,0.8067,0.1938,0.8053 T0.2212,0.8087 T0.2507,0.825 Q0.2614,0.8324,0.2744,0.8365 T0.3053,0.844 T0.332,0.8499 L0.303,0.894 L0.2998,0.8948 Q0.2904,0.8968,0.278,0.9009 T0.2554,0.9086 T0.233,0.9158 T0.2099,0.9213 T0.1868,0.9229 T0.1633,0.9192 Q0.1517,0.916,0.1367,0.9241 T0.1065,0.9462 T0.0753,0.9737 T0.041,0.9955 T0.0061,1 Q0,0.9984,0.0068,0.9845 Q0.009,0.9804,0.0123,0.9755 Q0.0187,0.9666,0.0261,0.9606 T0.0374,0.9555 Q0.0481,0.958,0.0629,0.949 T0.0931,0.9262 T0.1247,0.8987 T0.1597,0.8777 T0.1949,0.8752 Q0.213,0.8805,0.2343,0.8772 Q0.2249,0.8736,0.2172,0.8683 Q0.1914,0.8511,0.1738,0.8497 T0.1349,0.8617 Q0.1304,0.865,0.1273,0.8646 T0.1242,0.8605 L0.1239,0.8536 Q0.1226,0.8295,0.1354,0.8016 T0.1723,0.7496 Q0.1914,0.7308,0.2104,0.7219 Q0.2259,0.7145,0.2425,0.7141 T0.273,0.7182 T0.3022,0.7296 T0.3301,0.7439 T0.3569,0.7571 T0.3821,0.7643 Q0.373,0.7561,0.3598,0.7367 T0.3382,0.7088 L0.3327,0.7035 Q0.3282,0.699,0.3353,0.6884 T0.3517,0.6692 L0.3611,0.6607 Q0.3711,0.657,0.3751,0.6554 T0.3837,0.6507 T0.3898,0.6458 T0.3979,0.637 T0.4111,0.6236 Q0.4172,0.6175,0.4234,0.6138 Q0.4327,0.6081,0.4439,0.6077 T0.4655,0.6105 T0.4884,0.6201 T0.5113,0.633 T0.5347,0.6468 T0.5573,0.6578 Q0.5705,0.6635,0.5833,0.665 T0.6112,0.665 T0.6286,0.6635 Q0.6421,0.6631,0.6718,0.6501 Q0.626,0.6578,0.6089,0.6517 Q0.6063,0.6509,0.6002,0.6491 T0.5884,0.6454 T0.5757,0.6409 T0.561,0.6344 T0.5466,0.6256 Q0.536,0.6183,0.5108,0.5989 T0.4722,0.5712 T0.4527,0.564 L0.4418,0.5661 Q0.4308,0.5681,0.4366,0.5563 Q0.4401,0.5485,0.4476,0.5392 L0.455,0.5298 Q0.4631,0.5188,0.4714,0.51 T0.4866,0.4947 T0.5021,0.4831 T0.516,0.4749 T0.53,0.4694 T0.5428,0.4657 T0.5558,0.4637 T0.5674,0.4629 T0.5795,0.4625 Q0.5489,0.4543,0.534,0.4384 L0.5286,0.4327 Q0.5244,0.4282,0.5318,0.4174 T0.5486,0.398 L0.5579,0.3899 Q0.567,0.387,0.5749,0.3834 T0.5878,0.3772 T0.6012,0.3687 T0.6126,0.3603 T0.6268,0.3487 T0.6421,0.3361 Q0.6567,0.3242,0.6712,0.3159 T0.7009,0.3034 T0.7277,0.2973 T0.7556,0.2961 T0.7809,0.2977 T0.8074,0.301 Q0.8096,0.3014,0.8109,0.3016 T0.8143,0.302 T0.818,0.3026 Q0.8012,0.2936,0.7767,0.2781 Q0.6718,0.2117,0.5744,0.2476 Q0.5637,0.2516,0.5492,0.2604 T0.5218,0.2779 T0.4923,0.2965 T0.461,0.3124 T0.4282,0.3216 T0.394,0.3206 Q0.3637,0.3136,0.3474,0.2936 T0.3353,0.2459 Q0.3385,0.2247,0.355,0.2023 Q0.3646,0.1892,0.3763,0.179 Q0.3898,0.1672,0.4063,0.1595 Q0.3927,0.1562,0.3785,0.1485 Q0.3604,0.1387,0.3453,0.1354 T0.3161,0.1354 T0.2899,0.1458 T0.2627,0.166 Q0.262,0.1664,0.254,0.1733 T0.2414,0.1841 T0.2267,0.196 T0.2094,0.2086 T0.1915,0.2196 T0.1725,0.2288 T0.1546,0.2337 T0.1373,0.2343 T0.1223,0.228 T0.1097,0.2145 Q0.0962,0.1929,0.1258,0.1582 Q0.131,0.1517,0.1371,0.1464 Q0.1462,0.1387,0.156,0.1321 T0.1752,0.1205 T0.1878,0.1134 Q0.1891,0.1117,0.1947,0.103 T0.2035,0.0899 T0.2125,0.0781 T0.2243,0.064 Q0.1949,0.0616,0.1672,0.0828 Q0.153,0.0934,0.1412,0.1009 T0.1146,0.1146 T0.0873,0.1213 T0.0616,0.116 T0.0374,0.0962 Q0.0339,0.0909,0.0295,0.0846 T0.024,0.0767 T0.0202,0.0728 T0.0148,0.0704 T0.0068,0.0697 Q0.0003,0.0693,0.0058,0.0567 Q0.0077,0.0522,0.011,0.0469 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.1934,0.6075 Q0.2012,0.612,0.2188,0.6218 T0.2459,0.6369 T0.2718,0.6508 T0.2983,0.6644 T0.322,0.6758 T0.3447,0.6854 T0.363,0.6911 T0.3783,0.6936 Q0.3796,0.6936,0.396,0.6952 T0.4265,0.6952 T0.4548,0.6879 Q0.4656,0.6834,0.4805,0.6756 T0.5073,0.6614 T0.5327,0.6485 T0.5564,0.6389 T0.5759,0.6363 T0.5909,0.6422 Q0.5953,0.6467,0.604,0.6554 T0.6155,0.6669 T0.6251,0.6746 T0.6387,0.683 T0.6556,0.6903 L0.6498,0.6956 Q0.6414,0.7042,0.6319,0.7177 T0.6162,0.7403 T0.5979,0.7583 T0.5703,0.7703 Q0.5313,0.7785,0.4799,0.7605 Q0.5097,0.7817,0.5508,0.7911 T0.6302,0.8001 Q0.6431,0.7997,0.659,0.7942 T0.6888,0.7813 T0.7194,0.766 T0.7506,0.753 T0.7819,0.7472 T0.8131,0.754 Q0.8239,0.7589,0.8346,0.7681 T0.8544,0.7895 T0.8689,0.818 T0.8737,0.8507 L0.8733,0.858 Q0.8591,0.849,0.8474,0.845 T0.8242,0.8421 T0.8021,0.8482 T0.7772,0.8625 Q0.7653,0.8707,0.7513,0.8752 T0.7184,0.8831 T0.6908,0.889 Q0.7017,0.8915,0.7152,0.8956 T0.7396,0.9037 T0.7633,0.9115 T0.7872,0.9174 T0.8107,0.9194 T0.8347,0.9155 Q0.8493,0.9115,0.8671,0.9196 T0.9015,0.9421 T0.9345,0.9708 T0.9682,0.9943 T0.9997,1 Q0.999,0.94,0.9983,0.6508 T0.9976,0.2322 Q0.9976,0.2109,0.9992,0.1379 T1,0.0249 Q0.9949,0.0249,0.9917,0.0257 T0.9863,0.0273 T0.9817,0.0308 T0.9776,0.0357 T0.9721,0.0437 T0.9651,0.0539 Q0.956,0.0657,0.9456,0.072 T0.9247,0.0789 T0.904,0.0775 T0.8823,0.0694 T0.862,0.0573 T0.8422,0.0432 Q0.7856,0,0.7328,0.0318 Q0.7392,0.0318,0.7452,0.0324 T0.756,0.0345 T0.7657,0.0386 T0.7741,0.0437 T0.7821,0.0506 T0.7894,0.0588 T0.7966,0.0683 T0.8039,0.0787 T0.8119,0.0904 T0.8202,0.1028 Q0.8249,0.1093,0.849,0.1234 T0.8811,0.1448 Q0.8998,0.1612,0.8893,0.1783 Q0.8835,0.1877,0.8759,0.1928 T0.8593,0.1979 T0.8417,0.1967 T0.8222,0.1895 T0.8034,0.1791 T0.7848,0.1663 T0.7687,0.1534 T0.7543,0.141 T0.744,0.1322 Q0.7186,0.1106,0.6984,0.101 T0.6559,0.0926 T0.6082,0.1077 Q0.5899,0.1175,0.5733,0.1206 T0.5411,0.1202 T0.5122,0.1098 T0.4822,0.0914 Q0.4927,0.1032,0.5036,0.1122 T0.5276,0.1285 T0.5503,0.1401 T0.5777,0.1518 T0.6055,0.1636 Q0.6258,0.1726,0.6375,0.1863 T0.6519,0.2162 Q0.6556,0.2403,0.6392,0.2605 T0.5926,0.2876 Q0.5804,0.2909,0.5672,0.2899 T0.5428,0.2856 T0.5193,0.2764 T0.4966,0.264 T0.4744,0.2497 T0.4523,0.2352 T0.4301,0.2222 T0.4077,0.2118 Q0.3021,0.173,0.1903,0.2436 Q0.1669,0.2583,0.1504,0.2674 T0.1129,0.2846 T0.075,0.294 T0.0384,0.2911 T0,0.2746 Q0.021,0.297,0.0459,0.3082 T0.0953,0.3207 T0.146,0.3193 T0.1988,0.3131 T0.2508,0.3095 T0.3029,0.3176 T0.3529,0.3448 Q0.3566,0.348,0.3652,0.355 T0.3771,0.3645 T0.3877,0.3731 T0.3993,0.3815 T0.4103,0.3884 T0.4226,0.3953 T0.4352,0.4009 T0.4497,0.4064 L0.4443,0.4121 Q0.4372,0.4198,0.4277,0.4251 T0.4072,0.4337 T0.3867,0.439 T0.3656,0.4431 T0.3481,0.4468 Q0.3241,0.4529,0.2943,0.4463 Q0.3227,0.4623,0.3458,0.471 T0.3959,0.4814 Q0.404,0.4818,0.4187,0.4825 T0.4409,0.4835 T0.4605,0.4857 T0.4809,0.4908 T0.4992,0.4996 T0.5186,0.5141 T0.5371,0.5353 L0.5449,0.5451 L0.5337,0.543 Q0.5242,0.541,0.5091,0.55 T0.4683,0.5796 T0.4314,0.6083 Q0.4203,0.6161,0.4086,0.6212 T0.382,0.6308 T0.3667,0.6353 Q0.3485,0.6418,0.2995,0.633 T0.1934,0.6075 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.989,0.0469 Q0.9829,0.0379,0.9753,0.0316 T0.9635,0.0253 Q0.9574,0.0257,0.9539,0.0263 T0.9466,0.03 T0.9418,0.0345 T0.9355,0.0434 T0.9277,0.0546 Q0.9193,0.0653,0.9103,0.0712 T0.8919,0.0775 T0.8745,0.0765 T0.8564,0.0695 T0.8396,0.0593 T0.8228,0.0473 Q0.7612,0,0.7073,0.0326 L0.7386,0.0767 L0.7419,0.0767 Q0.7506,0.0767,0.7556,0.0773 T0.7641,0.0789 T0.7709,0.0842 T0.7767,0.0922 T0.7854,0.1054 T0.7974,0.1236 Q0.8057,0.1354,0.8157,0.1444 T0.8325,0.1574 T0.8474,0.1654 T0.8571,0.1701 Q0.8564,0.1717,0.8554,0.1733 Q0.8503,0.1815,0.844,0.186 T0.8301,0.1909 T0.8153,0.1903 T0.7993,0.1845 T0.7836,0.1758 T0.768,0.1648 T0.754,0.1535 T0.741,0.1425 T0.7312,0.1338 Q0.7031,0.1101,0.6805,0.0995 T0.6357,0.0901 T0.5886,0.1048 Q0.5721,0.1138,0.5574,0.1168 T0.5295,0.117 T0.5055,0.1089 T0.4814,0.0942 L0.4789,0.0926 L0.4711,0.0922 Q0.4589,0.095,0.4811,0.1203 Q0.495,0.1358,0.5087,0.1474 T0.5366,0.167 T0.5611,0.1796 T0.5879,0.1909 T0.6134,0.2011 Q0.6218,0.2047,0.6273,0.2092 Q0.6283,0.2117,0.6286,0.2141 Q0.6305,0.2263,0.6249,0.2388 T0.606,0.2616 T0.5747,0.2761 Q0.5602,0.2785,0.5487,0.2779 T0.5245,0.2724 T0.5015,0.2616 T0.4784,0.2476 T0.4547,0.2321 T0.4297,0.217 T0.4024,0.2043 Q0.2985,0.166,0.1901,0.2345 Q0.1694,0.248,0.1523,0.2569 T0.1165,0.2726 T0.0818,0.2812 T0.0494,0.2796 T0.0181,0.2663 L0.0116,0.2671 Q0,0.272,0.0232,0.2969 Q0.0468,0.3222,0.0742,0.3365 T0.1278,0.3542 T0.1807,0.3564 T0.233,0.352 T0.2815,0.3473 T0.3269,0.3509 T0.3656,0.3699 Q0.4037,0.4005,0.4153,0.4082 Q0.405,0.4139,0.3921,0.4174 T0.3633,0.4237 T0.3414,0.4278 Q0.3207,0.4331,0.2956,0.4278 Q0.2943,0.4278,0.293,0.4274 Q0.2933,0.429,0.2938,0.4313 T0.2964,0.4398 T0.3007,0.4513 T0.3069,0.4619 T0.3149,0.4698 Q0.3427,0.4857,0.3664,0.4947 T0.4169,0.5053 Q0.423,0.5057,0.4385,0.5065 T0.4606,0.508 T0.4779,0.51 T0.4958,0.5139 T0.5095,0.5204 Q0.4992,0.5232,0.485,0.5328 T0.451,0.5585 T0.4201,0.5824 Q0.4095,0.5897,0.3987,0.5946 T0.3738,0.6036 T0.3591,0.6081 Q0.3433,0.6134,0.2965,0.605 T0.1972,0.5816 L0.2026,0.5922 T0.2133,0.6134 L0.2188,0.624 Q0.2201,0.6248,0.2214,0.6252 Q0.2291,0.6297,0.2486,0.6405 T0.2786,0.657 T0.3066,0.6721 T0.3353,0.6866 T0.3598,0.6972 T0.383,0.7051 T0.4005,0.7076 Q0.4008,0.7076,0.4169,0.7092 T0.4471,0.7092 T0.475,0.7019 Q0.4895,0.6958,0.5176,0.6811 T0.5644,0.6586 T0.5921,0.6533 Q0.6128,0.6741,0.6173,0.6782 Q0.6131,0.6835,0.6068,0.6929 T0.5963,0.7078 T0.5858,0.7198 T0.5716,0.7304 T0.5534,0.7365 Q0.5179,0.7443,0.4708,0.728 L0.4708,0.7294 T0.471,0.7333 T0.4716,0.7392 T0.4734,0.7463 T0.4764,0.7539 T0.4814,0.7612 T0.4889,0.7679 Q0.4914,0.77,0.4944,0.7716 Q0.5534,0.8104,0.6409,0.8091 Q0.6521,0.8091,0.6654,0.8053 T0.6907,0.7959 T0.7157,0.7838 T0.741,0.772 T0.7652,0.7631 T0.7888,0.7594 T0.8106,0.7643 Q0.8196,0.7684,0.8267,0.7753 Q0.8409,0.7892,0.8399,0.8169 Q0.8216,0.8067,0.8062,0.8053 T0.7788,0.8087 T0.7493,0.825 Q0.7386,0.8324,0.7256,0.8365 T0.6947,0.844 T0.668,0.8499 L0.697,0.894 L0.7002,0.8948 Q0.7099,0.8968,0.7222,0.9009 T0.7446,0.9086 T0.767,0.9158 T0.7901,0.9213 T0.8132,0.9229 T0.8367,0.9192 Q0.8483,0.916,0.8633,0.9241 T0.8937,0.9462 T0.9248,0.9737 T0.959,0.9955 T0.9939,1 Q1,0.9984,0.9932,0.9845 Q0.991,0.9804,0.9877,0.9755 Q0.9813,0.9666,0.9739,0.9606 T0.9626,0.9555 Q0.9519,0.958,0.9372,0.949 T0.9071,0.9262 T0.8753,0.8987 T0.8403,0.8777 T0.8051,0.8752 Q0.787,0.8805,0.7657,0.8772 Q0.7751,0.8736,0.7828,0.8683 Q0.8086,0.8511,0.8262,0.8497 T0.8651,0.8617 Q0.8696,0.865,0.8727,0.8646 T0.8758,0.8605 L0.8761,0.8536 Q0.8774,0.8295,0.8646,0.8016 T0.8277,0.7496 Q0.8086,0.7308,0.7896,0.7219 Q0.7741,0.7145,0.7575,0.7141 T0.727,0.7182 T0.6978,0.7296 T0.6699,0.7439 T0.6433,0.7571 T0.6179,0.7643 Q0.627,0.7561,0.6402,0.7367 T0.6618,0.7088 L0.6673,0.7035 Q0.6718,0.699,0.6647,0.6884 T0.6483,0.6692 L0.6389,0.6607 Q0.6289,0.657,0.6249,0.6554 T0.6163,0.6507 T0.6102,0.6458 T0.6021,0.637 T0.5889,0.6236 Q0.5828,0.6175,0.5766,0.6138 Q0.5673,0.6081,0.5561,0.6077 T0.5345,0.6105 T0.5116,0.6201 T0.4887,0.633 T0.4653,0.6468 T0.4427,0.6578 Q0.4295,0.6635,0.4167,0.665 T0.3888,0.665 T0.3714,0.6635 Q0.3579,0.6631,0.3282,0.6501 Q0.374,0.6578,0.3911,0.6517 Q0.3937,0.6509,0.4024,0.6482 T0.4179,0.6434 T0.4348,0.6364 T0.4534,0.6256 Q0.464,0.6183,0.4892,0.5989 T0.5278,0.5712 T0.5476,0.564 L0.5582,0.5661 Q0.5692,0.5681,0.5634,0.5563 Q0.5599,0.5485,0.5524,0.5392 L0.545,0.5298 Q0.5369,0.5188,0.5286,0.51 T0.5134,0.4947 T0.4979,0.4831 T0.484,0.4749 T0.47,0.4694 T0.4572,0.4657 T0.4442,0.4637 T0.4326,0.4629 T0.4205,0.4625 Q0.4511,0.4543,0.466,0.4384 L0.4714,0.4327 Q0.4795,0.4237,0.4469,0.394 L0.4421,0.3899 Q0.433,0.387,0.4251,0.3834 T0.4122,0.3772 T0.3988,0.3687 T0.3874,0.3603 T0.3732,0.3487 T0.3579,0.3361 Q0.3433,0.3242,0.3288,0.3159 T0.2993,0.3034 T0.2725,0.2973 T0.2446,0.2961 T0.2193,0.2977 T0.1926,0.301 Q0.1855,0.3022,0.182,0.3026 Q0.1991,0.2936,0.2233,0.2781 Q0.3282,0.2117,0.4256,0.2476 Q0.4363,0.2516,0.451,0.2604 T0.4784,0.2779 T0.5077,0.2965 T0.539,0.3124 T0.5718,0.3216 T0.606,0.3206 Q0.6363,0.3136,0.6526,0.2936 T0.6647,0.2459 Q0.6615,0.2247,0.645,0.2023 Q0.6354,0.1892,0.6237,0.179 Q0.6102,0.1672,0.5937,0.1595 Q0.6073,0.1562,0.6215,0.1485 Q0.6396,0.1387,0.6547,0.1354 T0.6839,0.1354 T0.7101,0.1458 T0.7373,0.166 Q0.738,0.1664,0.746,0.1733 T0.7586,0.1841 T0.7733,0.196 T0.7907,0.2086 T0.8086,0.2196 T0.8275,0.2288 T0.8454,0.2337 T0.8627,0.2343 T0.8777,0.228 T0.8903,0.2145 Q0.9038,0.1929,0.8742,0.1582 Q0.869,0.1517,0.8629,0.1464 Q0.8538,0.1387,0.844,0.1321 T0.8248,0.1205 T0.8122,0.1134 Q0.8109,0.1117,0.8053,0.103 T0.7965,0.0899 T0.7875,0.0781 T0.7757,0.064 Q0.8051,0.0616,0.8328,0.0828 Q0.847,0.0934,0.8588,0.1009 T0.8854,0.1146 T0.9127,0.1213 T0.9384,0.116 T0.9626,0.0962 Q0.9664,0.0909,0.9706,0.0846 T0.976,0.0767 T0.9798,0.0728 T0.9852,0.0704 T0.9932,0.0697 Q0.9997,0.0693,0.9942,0.0567 Q0.9923,0.0522,0.989,0.0469 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C4}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.4116,0.0126 L0.4116,0.0776 L0.4871,0.0776 L0.4871,0.0126 L0.4116,0.0126 Z M0.4043,0 L0.4944,0 Q0.4973,0,0.4995,0.0021 T0.5017,0.0063 L0.5017,0.0839 Q0.5017,0.0864,0.4993,0.0883 T0.4944,0.0902 L0.4043,0.0902 Q0.4014,0.0902,0.3992,0.0881 T0.397,0.0839 L0.397,0.0063 Q0.397,0.0038,0.3994,0.0019 T0.4043,0 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.9099,0.0084 L0.9099,0.0818 L0.9951,0.0818 L0.9951,0.0084 L0.9099,0.0084 Z M0.9075,0.0042 L0.9976,0.0042 Q1,0.0042,1,0.0063 L1,0.0839 Q1,0.086,0.9976,0.086 L0.9075,0.086 Q0.905,0.086,0.905,0.0839 L0.905,0.0063 Q0.905,0.0042,0.9075,0.0042 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.66,0.0063 L0.7501,0.0063 L0.7501,0.0839 L0.66,0.0839 L0.66,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.5309,0.0063 L0.621,0.0063 L0.621,0.0839 L0.5309,0.0839 L0.5309,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.2689,0.0063 L0.359,0.0063 L0.359,0.0839 L0.2689,0.0839 L0.2689,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0.1374,0.0063 L0.2275,0.0063 L0.2275,0.0839 L0.1374,0.0839 L0.1374,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0,0.0063 L0.0901,0.0063 L0.0901,0.0839 L0,0.0839 L0,0.0063 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.4087,0.9224 L0.4087,0.9958 L0.4939,0.9958 L0.4939,0.9224 L0.4087,0.9224 Z M0.4062,0.9182 L0.4963,0.9182 Q0.4988,0.9182,0.4988,0.9203 L0.4988,0.9979 Q0.4988,1,0.4963,1 L0.4062,1 Q0.4038,1,0.4038,0.9979 L0.4038,0.9203 Q0.4038,0.9182,0.4062,0.9182 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0.1374,0.9203 L0.2275,0.9203 L0.2275,0.9979 L0.1374,0.9979 L0.1374,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0,0.9203 L0.0901,0.9203 L0.0901,0.9979 L0,0.9979 L0,0.9203 Z M0.9099,0.1158 L0.9099,0.1888 L0.9951,0.1888 L0.9951,0.1158 L0.9099,0.1158 Z M0.9075,0.1116 L0.9976,0.1116 Q1,0.1116,1,0.1137 L1,0.1909 Q1,0.193,0.9976,0.193 L0.9075,0.193 Q0.905,0.193,0.905,0.1909 L0.905,0.1137 Q0.905,0.1116,0.9075,0.1116 Z M0.7852,0.6086 L0.7852,0.682 L0.8704,0.682 L0.8704,0.6086 L0.7852,0.6086 Z M0.7828,0.6044 L0.8729,0.6044 Q0.8753,0.6044,0.8753,0.6065 L0.8753,0.6841 Q0.8753,0.6862,0.8729,0.6862 L0.7828,0.6862 Q0.7803,0.6862,0.7803,0.6841 L0.7803,0.6065 Q0.7803,0.6044,0.7828,0.6044 Z M0.66,0.1137 L0.7501,0.1137 L0.7501,0.1909 L0.66,0.1909 L0.66,0.1137 Z M0.6624,0.4102 L0.6624,0.4836 L0.7477,0.4836 L0.7477,0.4102 L0.6624,0.4102 Z M0.66,0.406 L0.7501,0.406 Q0.7526,0.406,0.7526,0.4081 L0.7526,0.4857 Q0.7526,0.4878,0.7501,0.4878 L0.66,0.4878 Q0.6576,0.4878,0.6576,0.4857 L0.6576,0.4081 Q0.6576,0.406,0.66,0.406 Z M0.6624,0.7131 L0.6624,0.7865 L0.7477,0.7865 L0.7477,0.7131 L0.6624,0.7131 Z M0.66,0.7089 L0.7501,0.7089 Q0.7526,0.7089,0.7526,0.711 L0.7526,0.7886 Q0.7526,0.7907,0.7501,0.7907 L0.66,0.7907 Q0.6576,0.7907,0.6576,0.7886 L0.6576,0.711 Q0.6576,0.7089,0.66,0.7089 Z M0.5407,0.2227 L0.5407,0.2836 L0.6113,0.2836 L0.6113,0.2227 L0.5407,0.2227 Z M0.5309,0.206 L0.621,0.206 Q0.623,0.206,0.6249,0.2068 T0.6281,0.2089 T0.6301,0.2116 T0.6308,0.2143 L0.6308,0.2919 Q0.6308,0.2957,0.6276,0.298 T0.621,0.3003 L0.5309,0.3003 Q0.5265,0.3003,0.5239,0.2976 T0.5212,0.2919 L0.5212,0.2143 Q0.5212,0.211,0.5244,0.2085 T0.5309,0.206 Z M0.5407,0.3184 L0.5407,0.3792 L0.6113,0.3792 L0.6113,0.3184 L0.5407,0.3184 Z M0.5309,0.3016 L0.621,0.3016 Q0.6254,0.3016,0.6281,0.3043 T0.6308,0.31 L0.6308,0.3876 Q0.6308,0.3914,0.6276,0.3937 T0.621,0.396 L0.5309,0.396 Q0.5265,0.396,0.5239,0.3932 T0.5212,0.3876 L0.5212,0.31 Q0.5212,0.3062,0.5244,0.3039 T0.5309,0.3016 Z M0.5309,0.4081 L0.621,0.4081 L0.621,0.4857 L0.5309,0.4857 L0.5309,0.4081 Z M0.5309,0.5059 L0.621,0.5059 L0.621,0.5835 L0.5309,0.5835 L0.5309,0.5059 Z M0.5407,0.6149 L0.5407,0.6758 L0.6113,0.6758 L0.6113,0.6149 L0.5407,0.6149 Z M0.5309,0.5982 L0.621,0.5982 Q0.6254,0.5982,0.6281,0.6009 T0.6308,0.6065 L0.6308,0.6841 Q0.6308,0.6879,0.6276,0.6902 T0.621,0.6925 L0.5309,0.6925 Q0.5265,0.6925,0.5239,0.6898 T0.5212,0.6841 L0.5212,0.6065 Q0.5212,0.6028,0.5244,0.6005 T0.5309,0.5982 Z M0.4062,0.1137 L0.4963,0.1137 L0.4963,0.1909 L0.4062,0.1909 L0.4062,0.1137 Z M0.4062,0.2143 L0.4963,0.2143 L0.4963,0.2919 L0.4062,0.2919 L0.4062,0.2143 Z M0.4184,0.4186 L0.4184,0.4753 L0.4842,0.4753 L0.4842,0.4186 L0.4184,0.4186 Z M0.4062,0.3977 L0.4963,0.3977 Q0.5017,0.3977,0.5051,0.401 T0.5085,0.4081 L0.5085,0.4857 Q0.5085,0.4904,0.5046,0.4933 T0.4963,0.4962 L0.4062,0.4962 Q0.4009,0.4962,0.3975,0.4929 T0.3941,0.4857 L0.3941,0.4081 Q0.3941,0.4035,0.398,0.4006 T0.4062,0.3977 Z M0.4062,0.5059 L0.4963,0.5059 L0.4963,0.5835 L0.4062,0.5835 L0.4062,0.5059 Z M0.4087,0.7131 L0.4087,0.7865 L0.4939,0.7865 L0.4939,0.7131 L0.4087,0.7131 Z M0.4062,0.7089 L0.4963,0.7089 Q0.4988,0.7089,0.4988,0.711 L0.4988,0.7886 Q0.4988,0.7907,0.4963,0.7907 L0.4062,0.7907 Q0.4038,0.7907,0.4038,0.7886 L0.4038,0.711 Q0.4038,0.7089,0.4062,0.7089 Z M0.2689,0.1137 L0.359,0.1137 L0.359,0.1909 L0.2689,0.1909 L0.2689,0.1137 Z M0.2689,0.2143 L0.359,0.2143 L0.359,0.2919 L0.2689,0.2919 L0.2689,0.2143 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.31 L0.359,0.31 L0.359,0.3876 L0.2689,0.3876 L0.2689,0.31 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.2689,0.4081 L0.359,0.4081 L0.359,0.4857 L0.2689,0.4857 L0.2689,0.4081 Z M0.2689,0.5059 L0.359,0.5059 L0.359,0.5835 L0.2689,0.5835 L0.2689,0.5059 Z M0.2689,0.711 L0.359,0.711 L0.359,0.7886 L0.2689,0.7886 L0.2689,0.711 Z M0.2689,0.8154 L0.359,0.8154 L0.359,0.8926 L0.2689,0.8926 L0.2689,0.8154 Z M0.1374,0.1137 L0.2275,0.1137 L0.2275,0.1909 L0.1374,0.1909 L0.1374,0.1137 Z M0.1374,0.2143 L0.2275,0.2143 L0.2275,0.2919 L0.1374,0.2919 L0.1374,0.2143 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.31 L0.2275,0.31 L0.2275,0.3876 L0.1374,0.3876 L0.1374,0.31 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0.1374,0.4081 L0.2275,0.4081 L0.2275,0.4857 L0.1374,0.4857 L0.1374,0.4081 Z M0.1374,0.5059 L0.2275,0.5059 L0.2275,0.5835 L0.1374,0.5835 L0.1374,0.5059 Z M0.1374,0.6065 L0.2275,0.6065 L0.2275,0.6841 L0.1374,0.6841 L0.1374,0.6065 Z M0.1374,0.711 L0.2275,0.711 L0.2275,0.7886 L0.1374,0.7886 L0.1374,0.711 Z M0.1374,0.8154 L0.2275,0.8154 L0.2275,0.8926 L0.1374,0.8926 L0.1374,0.8154 Z M0,0.1137 L0.0901,0.1137 L0.0901,0.1909 L0,0.1909 L0,0.1137 Z M0,0.2143 L0.0901,0.2143 L0.0901,0.2919 L0,0.2919 L0,0.2143 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.31 L0.0901,0.31 L0.0901,0.3876 L0,0.3876 L0,0.31 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z M0,0.4081 L0.0901,0.4081 L0.0901,0.4857 L0,0.4857 L0,0.4081 Z M0,0.5059 L0.0901,0.5059 L0.0901,0.5835 L0,0.5835 L0,0.5059 Z M0,0.6065 L0.0901,0.6065 L0.0901,0.6841 L0,0.6841 L0,0.6065 Z M0,0.711 L0.0901,0.711 L0.0901,0.7886 L0,0.7886 L0,0.711 Z M0,0.8154 L0.0901,0.8154 L0.0901,0.8926 L0,0.8926 L0,0.8154 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.5884,0.0126 L0.5884,0.0776 L0.5129,0.0776 L0.5129,0.0126 L0.5884,0.0126 Z M0.5957,0 L0.5056,0 Q0.5027,0,0.5005,0.0021 T0.4983,0.0063 L0.4983,0.0839 Q0.4983,0.0864,0.5007,0.0883 T0.5056,0.0902 L0.5957,0.0902 Q0.5986,0.0902,0.6008,0.0881 T0.603,0.0839 L0.603,0.0063 Q0.603,0.0038,0.6006,0.0019 T0.5957,0 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.0901,0.0084 L0.0901,0.0818 L0.0049,0.0818 L0.0049,0.0084 L0.0901,0.0084 Z M0.0925,0.0042 L0.0024,0.0042 Q0,0.0042,0,0.0063 L0,0.0839 Q0,0.086,0.0024,0.086 L0.0925,0.086 Q0.095,0.086,0.095,0.0839 L0.095,0.0063 Q0.095,0.0042,0.0925,0.0042 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.34,0.0063 L0.2499,0.0063 L0.2499,0.0839 L0.34,0.0839 L0.34,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.4691,0.0063 L0.379,0.0063 L0.379,0.0839 L0.4691,0.0839 L0.4691,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.7311,0.0063 L0.641,0.0063 L0.641,0.0839 L0.7311,0.0839 L0.7311,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M0.8626,0.0063 L0.7725,0.0063 L0.7725,0.0839 L0.8626,0.0839 L0.8626,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M1,0.0063 L0.9099,0.0063 L0.9099,0.0839 L1,0.0839 L1,0.0063 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.5913,0.9224 L0.5913,0.9958 L0.5061,0.9958 L0.5061,0.9224 L0.5913,0.9224 Z M0.5938,0.9182 L0.5037,0.9182 Q0.5012,0.9182,0.5012,0.9203 L0.5012,0.9979 Q0.5012,1,0.5037,1 L0.5938,1 Q0.5962,1,0.5962,0.9979 L0.5962,0.9203 Q0.5962,0.9182,0.5938,0.9182 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M0.8626,0.9203 L0.7725,0.9203 L0.7725,0.9979 L0.8626,0.9979 L0.8626,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M1,0.9203 L0.9099,0.9203 L0.9099,0.9979 L1,0.9979 L1,0.9203 Z M0.0901,0.1158 L0.0901,0.1888 L0.0049,0.1888 L0.0049,0.1158 L0.0901,0.1158 Z M0.0925,0.1116 L0.0024,0.1116 Q0,0.1116,0,0.1137 L0,0.1909 Q0,0.193,0.0024,0.193 L0.0925,0.193 Q0.095,0.193,0.095,0.1909 L0.095,0.1137 Q0.095,0.1116,0.0925,0.1116 Z M0.2148,0.6086 L0.2148,0.682 L0.1296,0.682 L0.1296,0.6086 L0.2148,0.6086 Z M0.2172,0.6044 L0.1271,0.6044 Q0.1247,0.6044,0.1247,0.6065 L0.1247,0.6841 Q0.1247,0.6862,0.1271,0.6862 L0.2172,0.6862 Q0.2197,0.6862,0.2197,0.6841 L0.2197,0.6065 Q0.2197,0.6044,0.2172,0.6044 Z M0.34,0.1137 L0.2499,0.1137 L0.2499,0.1909 L0.34,0.1909 L0.34,0.1137 Z M0.3376,0.4102 L0.3376,0.4836 L0.2523,0.4836 L0.2523,0.4102 L0.3376,0.4102 Z M0.34,0.406 L0.2499,0.406 Q0.2474,0.406,0.2474,0.4081 L0.2474,0.4857 Q0.2474,0.4878,0.2499,0.4878 L0.34,0.4878 Q0.3424,0.4878,0.3424,0.4857 L0.3424,0.4081 Q0.3424,0.406,0.34,0.406 Z M0.3376,0.7131 L0.3376,0.7865 L0.2523,0.7865 L0.2523,0.7131 L0.3376,0.7131 Z M0.34,0.7089 L0.2499,0.7089 Q0.2474,0.7089,0.2474,0.711 L0.2474,0.7886 Q0.2474,0.7907,0.2499,0.7907 L0.34,0.7907 Q0.3424,0.7907,0.3424,0.7886 L0.3424,0.711 Q0.3424,0.7089,0.34,0.7089 Z M0.4593,0.2227 L0.4593,0.2836 L0.3887,0.2836 L0.3887,0.2227 L0.4593,0.2227 Z M0.4691,0.206 L0.379,0.206 Q0.3746,0.206,0.3719,0.2087 T0.3692,0.2143 L0.3692,0.2919 Q0.3692,0.2957,0.3724,0.298 T0.379,0.3003 L0.4691,0.3003 Q0.4735,0.3003,0.4761,0.2976 T0.4788,0.2919 L0.4788,0.2143 Q0.4788,0.211,0.4756,0.2085 T0.4691,0.206 Z M0.4593,0.3184 L0.4593,0.3792 L0.3887,0.3792 L0.3887,0.3184 L0.4593,0.3184 Z M0.4691,0.3016 L0.379,0.3016 Q0.3746,0.3016,0.3719,0.3043 T0.3692,0.31 L0.3692,0.3876 Q0.3692,0.3914,0.3724,0.3937 T0.379,0.396 L0.4691,0.396 Q0.4735,0.396,0.4761,0.3932 T0.4788,0.3876 L0.4788,0.31 Q0.4788,0.3062,0.4756,0.3039 T0.4691,0.3016 Z M0.4691,0.4081 L0.379,0.4081 L0.379,0.4857 L0.4691,0.4857 L0.4691,0.4081 Z M0.4691,0.5059 L0.379,0.5059 L0.379,0.5835 L0.4691,0.5835 L0.4691,0.5059 Z M0.4593,0.6149 L0.4593,0.6758 L0.3887,0.6758 L0.3887,0.6149 L0.4593,0.6149 Z M0.4691,0.5982 L0.379,0.5982 Q0.3746,0.5982,0.3719,0.6009 T0.3692,0.6065 L0.3692,0.6841 Q0.3692,0.6879,0.3724,0.6902 T0.379,0.6925 L0.4691,0.6925 Q0.4735,0.6925,0.4761,0.6898 T0.4788,0.6841 L0.4788,0.6065 Q0.4788,0.6028,0.4756,0.6005 T0.4691,0.5982 Z M0.5938,0.1137 L0.5037,0.1137 L0.5037,0.1909 L0.5938,0.1909 L0.5938,0.1137 Z M0.5938,0.2143 L0.5037,0.2143 L0.5037,0.2919 L0.5938,0.2919 L0.5938,0.2143 Z M0.5816,0.4186 L0.5816,0.4753 L0.5158,0.4753 L0.5158,0.4186 L0.5816,0.4186 Z M0.5938,0.3977 L0.5037,0.3977 Q0.4983,0.3977,0.4949,0.401 T0.4915,0.4081 L0.4915,0.4857 Q0.4915,0.4904,0.4954,0.4933 T0.5037,0.4962 L0.5938,0.4962 Q0.5991,0.4962,0.6025,0.4929 T0.6059,0.4857 L0.6059,0.4081 Q0.6059,0.4035,0.602,0.4006 T0.5938,0.3977 Z M0.5938,0.5059 L0.5037,0.5059 L0.5037,0.5835 L0.5938,0.5835 L0.5938,0.5059 Z M0.5913,0.7131 L0.5913,0.7865 L0.5061,0.7865 L0.5061,0.7131 L0.5913,0.7131 Z M0.5938,0.7089 L0.5037,0.7089 Q0.5012,0.7089,0.5012,0.711 L0.5012,0.7886 Q0.5012,0.7907,0.5037,0.7907 L0.5938,0.7907 Q0.5962,0.7907,0.5962,0.7886 L0.5962,0.711 Q0.5962,0.7089,0.5938,0.7089 Z M0.7311,0.1137 L0.641,0.1137 L0.641,0.1909 L0.7311,0.1909 L0.7311,0.1137 Z M0.7311,0.2143 L0.641,0.2143 L0.641,0.2919 L0.7311,0.2919 L0.7311,0.2143 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.31 L0.641,0.31 L0.641,0.3876 L0.7311,0.3876 L0.7311,0.31 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.7311,0.4081 L0.641,0.4081 L0.641,0.4857 L0.7311,0.4857 L0.7311,0.4081 Z M0.7311,0.5059 L0.641,0.5059 L0.641,0.5835 L0.7311,0.5835 L0.7311,0.5059 Z M0.7311,0.711 L0.641,0.711 L0.641,0.7886 L0.7311,0.7886 L0.7311,0.711 Z M0.7311,0.8154 L0.641,0.8154 L0.641,0.8926 L0.7311,0.8926 L0.7311,0.8154 Z M0.8626,0.1137 L0.7725,0.1137 L0.7725,0.1909 L0.8626,0.1909 L0.8626,0.1137 Z M0.8626,0.2143 L0.7725,0.2143 L0.7725,0.2919 L0.8626,0.2919 L0.8626,0.2143 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.31 L0.7725,0.31 L0.7725,0.3876 L0.8626,0.3876 L0.8626,0.31 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M0.8626,0.4081 L0.7725,0.4081 L0.7725,0.4857 L0.8626,0.4857 L0.8626,0.4081 Z M0.8626,0.5059 L0.7725,0.5059 L0.7725,0.5835 L0.8626,0.5835 L0.8626,0.5059 Z M0.8626,0.6065 L0.7725,0.6065 L0.7725,0.6841 L0.8626,0.6841 L0.8626,0.6065 Z M0.8626,0.711 L0.7725,0.711 L0.7725,0.7886 L0.8626,0.7886 L0.8626,0.711 Z M0.8626,0.8154 L0.7725,0.8154 L0.7725,0.8926 L0.8626,0.8926 L0.8626,0.8154 Z M1,0.1137 L0.9099,0.1137 L0.9099,0.1909 L1,0.1909 L1,0.1137 Z M1,0.2143 L0.9099,0.2143 L0.9099,0.2919 L1,0.2919 L1,0.2143 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.31 L0.9099,0.31 L0.9099,0.3876 L1,0.3876 L1,0.31 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z M1,0.4081 L0.9099,0.4081 L0.9099,0.4857 L1,0.4857 L1,0.4081 Z M1,0.5059 L0.9099,0.5059 L0.9099,0.5835 L1,0.5835 L1,0.5059 Z M1,0.6065 L0.9099,0.6065 L0.9099,0.6841 L1,0.6841 L1,0.6065 Z M1,0.711 L0.9099,0.711 L0.9099,0.7886 L1,0.7886 L1,0.711 Z M1,0.8154 L0.9099,0.8154 L0.9099,0.8926 L1,0.8926 L1,0.8154 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.6529,0.018 L0.6529,0.112 L0.7724,0.112 L0.7724,0.018 L0.6529,0.018 Z M0.6411,0 L0.7837,0 Q0.7873,0,0.7902,0.0015 T0.7943,0.005 T0.7956,0.0092 L0.7956,0.1208 Q0.7956,0.125,0.7917,0.1275 T0.7837,0.13 L0.6411,0.13 Q0.6359,0.13,0.6326,0.1271 T0.6292,0.1208 L0.6292,0.0092 Q0.6292,0.005,0.6331,0.0025 T0.6411,0 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.4325,0.0143 L0.4325,0.1158 L0.5628,0.1158 L0.5628,0.0143 L0.4325,0.0143 Z M0.4264,0.0042 L0.5695,0.0042 Q0.5721,0.0042,0.5739,0.0057 T0.5757,0.0092 L0.5757,0.1208 Q0.5757,0.1221,0.5752,0.1231 T0.5736,0.1248 T0.5716,0.1256 T0.5695,0.1258 L0.4264,0.1258 Q0.4238,0.1258,0.422,0.1244 T0.4202,0.1208 L0.4202,0.0092 Q0.4202,0.008,0.4207,0.0069 T0.4222,0.0052 T0.4243,0.0044 T0.4264,0.0042 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0.2178,0.0092 L0.3605,0.0092 L0.3605,0.1208 L0.2178,0.1208 L0.2178,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0,0.0092 L0.1432,0.0092 L0.1432,0.1208 L0,0.1208 L0,0.0092 Z M0.8574,0.3217 L0.8574,0.4098 L0.9691,0.4098 L0.9691,0.3217 L0.8574,0.3217 Z M0.8419,0.2978 L0.9846,0.2978 Q0.9892,0.2978,0.9928,0.2997 T0.9982,0.3043 T1,0.31 L1,0.4216 Q1,0.427,0.9951,0.4304 T0.9846,0.4337 L0.8419,0.4337 Q0.8352,0.4337,0.8308,0.4299 T0.8265,0.4216 L0.8265,0.31 Q0.8265,0.3062,0.829,0.3035 T0.835,0.2993 T0.8419,0.2978 Z M0.8574,0.888 L0.8574,0.9757 L0.9691,0.9757 L0.9691,0.888 L0.8574,0.888 Z M0.8419,0.8637 L0.9846,0.8637 Q0.9892,0.8637,0.9928,0.8658 T0.9982,0.8706 T1,0.8758 L1,0.9878 Q1,0.9929,0.9951,0.9964 T0.9846,1 L0.8419,1 Q0.8352,1,0.8308,0.996 T0.8265,0.9878 L0.8265,0.8758 Q0.8265,0.8708,0.8314,0.8672 T0.8419,0.8637 Z M0.6509,0.169 L0.6509,0.2706 L0.7806,0.2706 L0.7806,0.169 L0.6509,0.169 Z M0.6442,0.159 L0.7868,0.159 Q0.7899,0.159,0.7917,0.1604 T0.7935,0.164 L0.7935,0.2756 Q0.7935,0.2781,0.7915,0.2796 T0.7868,0.281 L0.6442,0.281 Q0.6416,0.281,0.6395,0.2794 T0.6375,0.2756 L0.6375,0.164 Q0.6375,0.1619,0.6398,0.1604 T0.6442,0.159 Z M0.6632,0.6044 L0.6632,0.6858 L0.7678,0.6858 L0.7678,0.6044 L0.6632,0.6044 Z M0.6442,0.5742 L0.7868,0.5742 Q0.7951,0.5742,0.8007,0.5791 T0.8064,0.5893 L0.8064,0.7013 Q0.8064,0.7076,0.8002,0.712 T0.7868,0.7164 L0.6442,0.7164 Q0.6359,0.7164,0.6305,0.7114 T0.6251,0.7013 L0.6251,0.5893 Q0.6251,0.5847,0.6282,0.5812 T0.6357,0.5759 T0.6442,0.5742 Z M0.4264,0.164 L0.5695,0.164 L0.5695,0.2756 L0.4264,0.2756 L0.4264,0.164 Z M0.4325,0.315 L0.4325,0.4165 L0.5628,0.4165 L0.5628,0.315 L0.4325,0.315 Z M0.4264,0.3049 L0.5695,0.3049 Q0.5721,0.3049,0.5739,0.3064 T0.5757,0.31 L0.5757,0.4216 Q0.5757,0.4237,0.5736,0.4251 T0.5695,0.4266 L0.4264,0.4266 Q0.4238,0.4266,0.422,0.4251 T0.4202,0.4216 L0.4202,0.31 Q0.4202,0.3087,0.4207,0.3077 T0.4222,0.306 T0.4243,0.3052 T0.4264,0.3049 Z M0.4264,0.4476 L0.5695,0.4476 L0.5695,0.5596 L0.4264,0.5596 L0.4264,0.4476 Z M0.4392,0.7408 L0.4392,0.8326 L0.5561,0.8326 L0.5561,0.7408 L0.4392,0.7408 Z M0.4264,0.7206 L0.5695,0.7206 Q0.5747,0.7206,0.5783,0.724 T0.5819,0.7307 L0.5819,0.8427 Q0.5819,0.8469,0.5778,0.8498 T0.5695,0.8528 L0.4264,0.8528 Q0.4207,0.8528,0.4171,0.8496 T0.4135,0.8427 L0.4135,0.7307 Q0.4135,0.7265,0.4176,0.7236 T0.4264,0.7206 Z M0.2178,0.164 L0.3605,0.164 L0.3605,0.2756 L0.2178,0.2756 L0.2178,0.164 Z M0.2178,0.31 L0.3605,0.31 L0.3605,0.4216 L0.2178,0.4216 L0.2178,0.31 Z M0.2178,0.5893 L0.3605,0.5893 L0.3605,0.7013 L0.2178,0.7013 L0.2178,0.5893 Z M0.2178,0.7307 L0.3605,0.7307 L0.3605,0.8427 L0.2178,0.8427 L0.2178,0.7307 Z M0.2178,0.8758 L0.3605,0.8758 L0.3605,0.9878 L0.2178,0.9878 L0.2178,0.8758 Z M0,0.164 L0.1432,0.164 L0.1432,0.2756 L0,0.2756 L0,0.164 Z M0,0.31 L0.1432,0.31 L0.1432,0.4216 L0,0.4216 L0,0.31 Z M0,0.4476 L0.1432,0.4476 L0.1432,0.5596 L0,0.5596 L0,0.4476 Z M0,0.5893 L0.1432,0.5893 L0.1432,0.7013 L0,0.7013 L0,0.5893 Z M0,0.7307 L0.1432,0.7307 L0.1432,0.8427 L0,0.8427 L0,0.7307 Z M0,0.8758 L0.1432,0.8758 L0.1432,0.9878 L0,0.9878 L0,0.8758 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.3476,0.018 L0.3476,0.112 L0.2276,0.112 L0.2276,0.018 L0.3476,0.018 Z M0.3594,0 L0.2163,0 Q0.2132,0,0.2104,0.0015 T0.2062,0.005 T0.2049,0.0092 L0.2049,0.1208 Q0.2049,0.1237,0.2067,0.1258 T0.2111,0.129 T0.2163,0.13 L0.3594,0.13 Q0.3641,0.13,0.3674,0.1271 T0.3708,0.1208 L0.3708,0.0092 Q0.3708,0.005,0.3669,0.0025 T0.3594,0 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.5675,0.0143 L0.5675,0.1158 L0.4377,0.1158 L0.4377,0.0143 L0.5675,0.0143 Z M0.5736,0.0042 L0.431,0.0042 Q0.4284,0.0042,0.4264,0.0057 T0.4243,0.0092 L0.4243,0.1208 Q0.4243,0.1221,0.4251,0.1231 T0.4269,0.1248 T0.4289,0.1256 T0.431,0.1258 L0.5736,0.1258 Q0.5767,0.1258,0.5785,0.1244 T0.5803,0.1208 L0.5803,0.0092 Q0.5803,0.008,0.5798,0.0069 T0.5783,0.0052 T0.5762,0.0044 T0.5736,0.0042 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M0.7827,0.0092 L0.6395,0.0092 L0.6395,0.1208 L0.7827,0.1208 L0.7827,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M1,0.0092 L0.8574,0.0092 L0.8574,0.1208 L1,0.1208 L1,0.0092 Z M0.1432,0.3217 L0.1432,0.4098 L0.0309,0.4098 L0.0309,0.3217 L0.1432,0.3217 Z M0.1586,0.2978 L0.0154,0.2978 Q0.0088,0.2978,0.0044,0.3016 T0,0.31 L0,0.4216 Q0,0.427,0.0049,0.4304 T0.0154,0.4337 L0.1586,0.4337 Q0.1648,0.4337,0.1694,0.4299 T0.174,0.4216 L0.174,0.31 Q0.174,0.3062,0.1715,0.3035 T0.1653,0.2993 T0.1586,0.2978 Z M0.1432,0.888 L0.1432,0.9757 L0.0309,0.9757 L0.0309,0.888 L0.1432,0.888 Z M0.1586,0.8637 L0.0154,0.8637 Q0.0108,0.8637,0.0072,0.8658 T0.0018,0.8706 T0,0.8758 L0,0.9878 Q0,0.9929,0.0049,0.9964 T0.0154,1 L0.1586,1 Q0.1648,1,0.1694,0.996 T0.174,0.9878 L0.174,0.8758 Q0.174,0.8725,0.1715,0.8695 T0.1653,0.8651 T0.1586,0.8637 Z M0.3496,0.169 L0.3496,0.2706 L0.2194,0.2706 L0.2194,0.169 L0.3496,0.169 Z M0.3563,0.159 L0.2132,0.159 Q0.2106,0.159,0.2088,0.1604 T0.207,0.164 L0.207,0.2756 Q0.207,0.2781,0.2091,0.2796 T0.2132,0.281 L0.3563,0.281 Q0.3589,0.281,0.3607,0.2794 T0.3625,0.2756 L0.3625,0.164 Q0.3625,0.1619,0.3605,0.1604 T0.3563,0.159 Z M0.3368,0.6044 L0.3368,0.6858 L0.2327,0.6858 L0.2327,0.6044 L0.3368,0.6044 Z M0.3563,0.5742 L0.2132,0.5742 Q0.2049,0.5742,0.1993,0.5791 T0.1936,0.5893 L0.1936,0.7013 Q0.1936,0.7076,0.2001,0.712 T0.2132,0.7164 L0.3563,0.7164 Q0.3641,0.7164,0.3697,0.7114 T0.3754,0.7013 L0.3754,0.5893 Q0.3754,0.5847,0.3723,0.5812 T0.3648,0.5759 T0.3563,0.5742 Z M0.5736,0.164 L0.431,0.164 L0.431,0.2756 L0.5736,0.2756 L0.5736,0.164 Z M0.5675,0.315 L0.5675,0.4165 L0.4377,0.4165 L0.4377,0.315 L0.5675,0.315 Z M0.5736,0.3049 L0.431,0.3049 Q0.4284,0.3049,0.4264,0.3064 T0.4243,0.31 L0.4243,0.4216 Q0.4243,0.4237,0.4266,0.4251 T0.431,0.4266 L0.5736,0.4266 Q0.5767,0.4266,0.5785,0.4251 T0.5803,0.4216 L0.5803,0.31 Q0.5803,0.3087,0.5798,0.3077 T0.5783,0.306 T0.5762,0.3052 T0.5736,0.3049 Z M0.5736,0.4476 L0.431,0.4476 L0.431,0.5596 L0.5736,0.5596 L0.5736,0.4476 Z M0.5613,0.7408 L0.5613,0.8326 L0.4439,0.8326 L0.4439,0.7408 L0.5613,0.7408 Z M0.5736,0.7206 L0.431,0.7206 Q0.4253,0.7206,0.4217,0.724 T0.4181,0.7307 L0.4181,0.8427 Q0.4181,0.8469,0.4222,0.8498 T0.431,0.8528 L0.5736,0.8528 Q0.5778,0.8528,0.5808,0.8511 T0.5855,0.8471 T0.587,0.8427 L0.587,0.7307 Q0.587,0.7265,0.5826,0.7236 T0.5736,0.7206 Z M0.7827,0.164 L0.6395,0.164 L0.6395,0.2756 L0.7827,0.2756 L0.7827,0.164 Z M0.7827,0.31 L0.6395,0.31 L0.6395,0.4216 L0.7827,0.4216 L0.7827,0.31 Z M0.7827,0.5893 L0.6395,0.5893 L0.6395,0.7013 L0.7827,0.7013 L0.7827,0.5893 Z M0.7827,0.7307 L0.6395,0.7307 L0.6395,0.8427 L0.7827,0.8427 L0.7827,0.7307 Z M0.7827,0.8758 L0.6395,0.8758 L0.6395,0.9878 L0.7827,0.9878 L0.7827,0.8758 Z M1,0.164 L0.8574,0.164 L0.8574,0.2756 L1,0.2756 L1,0.164 Z M1,0.31 L0.8574,0.31 L0.8574,0.4216 L1,0.4216 L1,0.31 Z M1,0.4476 L0.8574,0.4476 L0.8574,0.5596 L1,0.5596 L1,0.4476 Z M1,0.5893 L0.8574,0.5893 L0.8574,0.7013 L1,0.7013 L1,0.5893 Z M1,0.7307 L0.8574,0.7307 L0.8574,0.8427 L1,0.8427 L1,0.7307 Z M1,0.8758 L0.8574,0.8758 L0.8574,0.9878 L1,0.9878 L1,0.8758 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0C8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.0008,0.9979 Q0.0008,1,0.0032,1 Q0.0482,1,0.0602,0.9975 Q0.0667,0.9963,0.0847,0.9917 T0.1084,0.9859 Q0.1124,0.9851,0.1221,0.9851 L0.1253,0.9851 L0.1285,0.9851 Q0.1518,0.9851,0.1655,0.9809 Q0.1695,0.9801,0.1731,0.9788 T0.1791,0.9766 T0.1839,0.9747 T0.1888,0.9732 L0.1936,0.972 T0.1988,0.9712 T0.2048,0.971 L0.2096,0.971 Q0.2201,0.9714,0.2233,0.9714 T0.2345,0.9693 Q0.2386,0.9685,0.2502,0.9674 T0.2715,0.966 L0.2811,0.9656 Q0.2916,0.9647,0.2916,0.9573 Q0.2916,0.954,0.2892,0.9515 T0.2819,0.9486 Q0.2755,0.9482,0.2683,0.9482 Q0.2643,0.9482,0.2582,0.9484 T0.2498,0.9486 Q0.2426,0.9486,0.2402,0.9473 Q0.2386,0.9469,0.2317,0.943 T0.2161,0.937 Q0.2104,0.9357,0.1992,0.933 T0.1831,0.9291 Q0.1807,0.9287,0.1783,0.9285 T0.1739,0.928 T0.1703,0.9276 T0.1675,0.9272 T0.1655,0.9266 T0.1639,0.9253 Q0.1598,0.9216,0.1462,0.9216 L0.143,0.9216 L0.1398,0.9216 L0.1365,0.9216 Q0.1197,0.9195,0.1197,0.9154 L0.1197,0.9154 Q0.1205,0.9121,0.1373,0.9088 T0.1703,0.9054 Q0.1791,0.9054,0.1863,0.904 T0.1984,0.9011 T0.2064,0.8992 Q0.2402,0.8967,0.2506,0.8934 Q0.2538,0.8926,0.2602,0.8899 T0.2707,0.8872 Q0.2715,0.8872,0.2731,0.8876 Q0.2795,0.8884,0.2884,0.8884 Q0.3068,0.8884,0.3092,0.8826 L0.3092,0.8797 Q0.3092,0.8772,0.3076,0.8747 T0.3044,0.8706 T0.3028,0.8681 Q0.3076,0.8656,0.3237,0.8656 Q0.3317,0.8656,0.3622,0.8623 Q0.3655,0.8623,0.3867,0.8617 T0.4217,0.8602 Q0.4241,0.8598,0.4289,0.8598 L0.4345,0.8598 L0.441,0.8598 Q0.4578,0.8598,0.4667,0.8586 Q0.4739,0.8573,0.4851,0.8567 T0.5004,0.8552 Q0.5012,0.8552,0.5108,0.855 T0.5269,0.853 T0.5333,0.8461 Q0.5333,0.8445,0.5325,0.8432 T0.5309,0.8409 T0.5289,0.8391 T0.5261,0.8378 L0.5229,0.837 T0.5185,0.8362 T0.5133,0.8355 T0.5076,0.8349 T0.502,0.8341 Q0.4932,0.8324,0.4859,0.8324 Q0.4827,0.8324,0.4787,0.8328 T0.4731,0.8333 Q0.4707,0.8333,0.4683,0.8324 Q0.4482,0.825,0.4394,0.825 Q0.4369,0.825,0.4345,0.8254 Q0.4225,0.827,0.4153,0.827 Q0.4088,0.827,0.4064,0.8258 Q0.3976,0.8204,0.3751,0.8204 Q0.3703,0.8204,0.3667,0.8214 T0.3622,0.8225 Q0.3598,0.8225,0.3558,0.82 Q0.351,0.8175,0.3402,0.8167 T0.3177,0.8156 T0.3028,0.8146 L0.2956,0.8121 T0.2871,0.8094 T0.2771,0.8073 T0.2635,0.8063 Q0.261,0.8063,0.2586,0.8067 Q0.2378,0.8071,0.2273,0.8071 Q0.2072,0.8071,0.2024,0.8051 Q0.1976,0.8034,0.1932,0.8026 T0.1867,0.8013 T0.1847,0.8001 L0.1847,0.7993 Q0.1855,0.7984,0.1855,0.7978 T0.1859,0.7966 T0.1867,0.7959 T0.188,0.7955 T0.1896,0.7951 T0.1916,0.7949 T0.1944,0.7945 T0.1984,0.7941 T0.2032,0.7939 Q0.212,0.7926,0.2325,0.7918 T0.2562,0.7905 Q0.2691,0.7893,0.2787,0.7847 Q0.2835,0.7827,0.2968,0.7791 T0.3149,0.7756 Q0.3189,0.7756,0.3305,0.7733 T0.3446,0.771 L0.3454,0.771 Q0.3462,0.771,0.3498,0.7721 T0.3574,0.774 T0.3655,0.7748 Q0.3703,0.7748,0.3743,0.7731 T0.3823,0.7708 T0.3916,0.77 T0.3992,0.7694 Q0.4008,0.7694,0.4076,0.7688 T0.4205,0.7675 L0.4305,0.7665 Q0.4345,0.7657,0.4402,0.7617 T0.4458,0.7545 Q0.4458,0.7503,0.4353,0.7487 Q0.4297,0.7478,0.4209,0.7462 T0.4044,0.7433 T0.3904,0.7416 L0.3847,0.7416 Q0.3807,0.7416,0.3747,0.7422 T0.3663,0.7428 T0.3606,0.7424 Q0.3582,0.742,0.3562,0.7418 T0.3522,0.7412 T0.3482,0.7406 T0.3446,0.7401 T0.341,0.7397 T0.3373,0.7393 T0.3341,0.7391 L0.3293,0.7391 Q0.3277,0.7395,0.3253,0.7395 Q0.3205,0.7395,0.3052,0.7387 T0.2835,0.7379 Q0.2755,0.7379,0.253,0.7325 Q0.2506,0.7316,0.2462,0.731 T0.2382,0.7296 T0.2305,0.7271 T0.2181,0.7234 T0.2048,0.7196 T0.2,0.7155 Q0.2,0.7142,0.2016,0.713 Q0.208,0.7059,0.2285,0.6978 T0.2618,0.6898 Q0.2739,0.6898,0.2831,0.6848 T0.2956,0.6794 Q0.3028,0.6786,0.3289,0.6765 T0.3566,0.674 Q0.3582,0.674,0.3855,0.6723 T0.4406,0.6688 T0.4755,0.6653 Q0.5004,0.6607,0.5124,0.6599 L0.5165,0.6599 Q0.5213,0.6599,0.5277,0.6605 T0.5373,0.6611 Q0.543,0.6611,0.5494,0.6599 Q0.592,0.6524,0.6,0.6524 Q0.6064,0.6524,0.61,0.6499 T0.6161,0.6474 Q0.6177,0.6474,0.6261,0.6495 T0.6418,0.6516 L0.6482,0.6516 Q0.653,0.6512,0.6651,0.6506 T0.6876,0.6493 T0.7084,0.6477 T0.7229,0.645 Q0.7269,0.6437,0.7333,0.6437 Q0.7373,0.6437,0.7458,0.6443 T0.7574,0.645 Q0.7614,0.645,0.7631,0.6445 T0.7655,0.6441 Q0.7671,0.6441,0.7723,0.6448 T0.7823,0.6454 Q0.788,0.6454,0.7952,0.6445 Q0.8233,0.6408,0.8723,0.6367 Q0.9012,0.6342,0.9092,0.6342 L0.9165,0.6342 L0.9205,0.6342 Q0.9277,0.6342,0.9382,0.6323 T0.9526,0.6304 Q0.9534,0.6304,0.9594,0.6307 T0.9695,0.6309 Q0.9976,0.6309,0.9992,0.6234 Q1,0.6201,1,0.618 Q1,0.6089,0.9775,0.608 Q0.9703,0.6076,0.9582,0.6076 Q0.9518,0.6076,0.9394,0.6078 T0.9213,0.608 Q0.9036,0.608,0.8972,0.6072 Q0.894,0.6068,0.8871,0.6058 T0.8751,0.6039 T0.8627,0.6024 T0.8506,0.6018 L0.8474,0.6018 Q0.841,0.6022,0.8281,0.6041 T0.8112,0.606 Q0.8096,0.606,0.808,0.6056 Q0.7783,0.6002,0.7647,0.6002 Q0.7598,0.6002,0.7454,0.6006 T0.7261,0.601 Q0.7221,0.601,0.7145,0.6022 T0.7036,0.6035 Q0.6996,0.6035,0.6964,0.6022 Q0.6723,0.5902,0.6442,0.5902 Q0.6426,0.5902,0.6386,0.5904 T0.6337,0.5906 Q0.6273,0.5906,0.6249,0.5894 L0.6177,0.5856 T0.6116,0.5838 T0.6016,0.5825 T0.5855,0.5823 Q0.5719,0.5823,0.5574,0.5798 T0.5373,0.5774 L0.5341,0.5774 Q0.5293,0.5778,0.5213,0.5786 T0.51,0.5794 Q0.5036,0.5794,0.4988,0.5769 Q0.4932,0.574,0.4771,0.574 Q0.4723,0.574,0.4631,0.5745 T0.4498,0.5749 Q0.4418,0.5749,0.4394,0.5736 Q0.4353,0.5724,0.4305,0.5715 T0.4225,0.5707 T0.4145,0.5705 T0.4056,0.5699 Q0.4008,0.5691,0.398,0.5689 T0.394,0.5689 T0.3908,0.5686 T0.3855,0.5674 Q0.3831,0.567,0.3811,0.5662 T0.3779,0.5645 T0.3743,0.563 T0.3695,0.5624 Q0.3655,0.5624,0.3594,0.5635 T0.3494,0.5645 T0.3406,0.5635 T0.3325,0.5624 Q0.3309,0.5624,0.3301,0.5628 L0.3141,0.5628 Q0.2635,0.5628,0.2506,0.5612 Q0.2474,0.5608,0.2434,0.5608 Q0.2402,0.5608,0.2349,0.561 T0.2273,0.5612 Q0.2209,0.5612,0.2177,0.5595 Q0.2145,0.557,0.2145,0.5558 Q0.2169,0.5537,0.2345,0.5521 Q0.2876,0.5475,0.3028,0.5425 Q0.3068,0.5413,0.3149,0.5396 T0.3309,0.5367 T0.3478,0.5338 T0.3606,0.5309 Q0.3631,0.5305,0.3755,0.5259 T0.3968,0.5214 Q0.3992,0.5214,0.4024,0.5218 T0.4096,0.5222 Q0.4161,0.5222,0.4213,0.5209 T0.4313,0.5185 T0.4402,0.5172 Q0.4418,0.5168,0.447,0.5168 L0.4558,0.5168 T0.4639,0.5162 T0.4707,0.5141 T0.4731,0.5102 Q0.4731,0.5089,0.4723,0.5081 Q0.4699,0.5019,0.4627,0.4994 T0.4369,0.4965 Q0.4273,0.4961,0.4193,0.4946 T0.4048,0.4915 T0.3952,0.4894 Q0.392,0.4886,0.388,0.4886 Q0.3847,0.4886,0.3775,0.4892 T0.3647,0.4898 Q0.3598,0.4898,0.355,0.4894 Q0.3165,0.4861,0.3141,0.4853 Q0.2779,0.4774,0.2715,0.4774 Q0.2699,0.4774,0.2683,0.4776 T0.2627,0.4778 Q0.2498,0.4778,0.2281,0.4762 Q0.2241,0.4749,0.2233,0.4737 Q0.2249,0.472,0.2321,0.4708 Q0.2402,0.4695,0.2554,0.4695 L0.2675,0.4695 L0.2795,0.4695 Q0.294,0.4695,0.2996,0.4679 Q0.3141,0.4629,0.3373,0.4616 Q0.3414,0.4612,0.3466,0.4606 T0.3582,0.4589 T0.3687,0.4562 T0.3727,0.4525 Q0.3727,0.4509,0.3703,0.4492 Q0.3639,0.4455,0.3474,0.4419 T0.3213,0.4384 L0.3157,0.4384 L0.31,0.4384 Q0.294,0.4384,0.2859,0.4363 Q0.2795,0.4347,0.2691,0.4347 L0.2618,0.4347 L0.2546,0.4347 Q0.2458,0.4347,0.2394,0.4338 Q0.2257,0.4318,0.2193,0.4303 T0.2092,0.4266 T0.2052,0.4224 T0.2024,0.4154 T0.196,0.4052 Q0.1912,0.3994,0.1867,0.3949 T0.1807,0.3884 T0.1791,0.3849 Q0.1791,0.3824,0.1839,0.3745 Q0.1871,0.3687,0.1944,0.3637 T0.2072,0.3561 T0.2221,0.3501 T0.2321,0.3463 Q0.2353,0.3455,0.241,0.3445 T0.2522,0.3428 T0.2639,0.3411 T0.2731,0.3384 T0.2956,0.3351 T0.3197,0.3326 Q0.3213,0.3322,0.3237,0.3322 T0.3325,0.3331 T0.3414,0.3339 Q0.3478,0.3339,0.3518,0.3314 Q0.3606,0.3264,0.3703,0.3264 Q0.3775,0.3264,0.3847,0.3293 Q0.3888,0.331,0.396,0.331 Q0.4024,0.331,0.4116,0.3295 T0.4265,0.3266 T0.4414,0.3227 T0.4514,0.3202 Q0.4578,0.3181,0.4839,0.3152 T0.5112,0.3123 T0.5177,0.3119 T0.5285,0.3111 T0.5398,0.3096 T0.549,0.3065 T0.5526,0.3015 Q0.5526,0.2999,0.5518,0.2982 Q0.547,0.2903,0.5386,0.2883 T0.5165,0.2862 Q0.5116,0.2862,0.5056,0.2866 T0.4972,0.287 Q0.4884,0.287,0.4787,0.2833 Q0.4594,0.2758,0.4265,0.2758 Q0.4169,0.2758,0.4129,0.2766 Q0.4112,0.2771,0.4104,0.2771 Q0.4088,0.2771,0.4056,0.2748 T0.3968,0.2721 Q0.3695,0.2696,0.359,0.2659 Q0.355,0.2646,0.351,0.2646 T0.3426,0.2661 T0.3365,0.2675 T0.3333,0.2659 Q0.3277,0.2605,0.3116,0.2553 T0.2859,0.2501 L0.2843,0.2501 L0.2819,0.2501 Q0.2779,0.2501,0.2723,0.2493 T0.2586,0.2472 T0.2474,0.2455 Q0.1799,0.236,0.1719,0.236 Q0.1663,0.236,0.1606,0.235 T0.1502,0.2316 T0.1454,0.2265 Q0.1454,0.2223,0.1558,0.2173 Q0.1679,0.2115,0.1859,0.2092 T0.2185,0.207 T0.2353,0.2057 Q0.2369,0.2049,0.2502,0.2041 T0.2799,0.2022 T0.302,0.2007 Q0.3084,0.1999,0.3245,0.1954 T0.347,0.1908 Q0.3502,0.1908,0.3534,0.1916 Q0.3663,0.197,0.388,0.197 Q0.404,0.197,0.4088,0.1929 Q0.4096,0.192,0.4108,0.192 T0.4177,0.1945 T0.4277,0.197 T0.4369,0.1949 Q0.4442,0.1908,0.4843,0.1875 Q0.4892,0.1871,0.4968,0.1839 T0.5084,0.1808 L0.5108,0.1808 Q0.5213,0.1825,0.5454,0.1825 Q0.5823,0.1825,0.5992,0.1779 Q0.6096,0.1746,0.6217,0.1746 Q0.6249,0.1746,0.6309,0.1748 T0.6394,0.175 Q0.6434,0.175,0.6466,0.1746 Q0.6474,0.1746,0.6594,0.1738 T0.6787,0.1713 T0.6892,0.1659 Q0.6924,0.1634,0.6924,0.1593 Q0.6924,0.1547,0.6888,0.151 T0.6795,0.1472 Q0.6779,0.1472,0.6763,0.1477 Q0.6699,0.1493,0.6667,0.1493 Q0.661,0.1493,0.6506,0.1456 Q0.6329,0.1394,0.6129,0.1394 Q0.6048,0.1394,0.5992,0.1377 T0.5888,0.136 T0.5803,0.1377 T0.5751,0.1394 Q0.5711,0.1394,0.5639,0.1369 Q0.5365,0.1273,0.5116,0.1273 Q0.4876,0.1273,0.4667,0.1253 Q0.4627,0.1248,0.4542,0.1211 T0.4337,0.1174 L0.4305,0.1174 Q0.4217,0.1178,0.4153,0.119 T0.408,0.1203 Q0.4056,0.1203,0.4,0.117 Q0.3976,0.1153,0.396,0.1136 T0.3932,0.1112 T0.39,0.1099 T0.3847,0.1095 Q0.3799,0.1095,0.3687,0.1103 Q0.355,0.1112,0.3434,0.1141 T0.3301,0.117 Q0.3261,0.117,0.3229,0.1145 Q0.3181,0.1116,0.3052,0.1087 T0.2787,0.1043 T0.2546,0.1029 Q0.2506,0.1029,0.2474,0.1033 Q0.2418,0.1037,0.2349,0.1054 T0.2249,0.107 Q0.2225,0.107,0.2193,0.1066 Q0.2024,0.1024,0.1863,0.1024 Q0.1823,0.1024,0.1562,0.0971 T0.1269,0.0917 T0.1161,0.0896 T0.106,0.0854 Q0.1076,0.0846,0.11,0.0842 Q0.1173,0.0821,0.1261,0.0821 Q0.1301,0.0821,0.1369,0.0825 T0.1462,0.083 Q0.1494,0.083,0.151,0.0825 T0.1831,0.0813 T0.2201,0.0784 T0.243,0.074 T0.2667,0.0718 Q0.2707,0.0718,0.2755,0.0724 T0.2843,0.073 T0.2936,0.0724 T0.3024,0.0705 T0.3072,0.068 T0.3092,0.0657 T0.3108,0.0647 Q0.3116,0.0643,0.3133,0.0643 L0.3157,0.0643 L0.3181,0.0643 Q0.3446,0.0643,0.3446,0.0556 T0.3205,0.0469 L0.3189,0.0469 Q0.3157,0.0469,0.3157,0.0465 Q0.3084,0.0427,0.2835,0.0386 Q0.2739,0.0369,0.2675,0.0342 T0.2538,0.0299 Q0.249,0.029,0.245,0.029 T0.2378,0.0303 T0.2321,0.0315 Q0.2305,0.0315,0.2273,0.0307 Q0.2201,0.0286,0.2004,0.0272 T0.1775,0.0245 Q0.1735,0.0228,0.1663,0.0226 T0.1522,0.0197 T0.1345,0.0145 T0.1124,0.0079 Q0.0996,0.0033,0.0671,0.0044 T0.0297,0.0041 Q0.0169,0,0.0024,0.0017 Q0,0.0021,0,0.0037 Z M0.0064,0.9954 L0.0056,0.0058 Q0.0177,0.005,0.0281,0.0083 Q0.0345,0.01,0.0675,0.0089 T0.1108,0.012 Q0.1221,0.0162,0.1329,0.0187 T0.1498,0.0236 T0.1606,0.0268 T0.1691,0.0274 T0.1759,0.0286 Q0.1807,0.0303,0.2,0.0315 T0.2257,0.0348 Q0.2305,0.0361,0.2337,0.0361 Q0.2361,0.0361,0.2398,0.0348 T0.2466,0.0336 Q0.249,0.0336,0.2522,0.034 Q0.2586,0.0353,0.2655,0.0382 T0.2827,0.0427 Q0.3068,0.0469,0.3124,0.0502 Q0.3157,0.0523,0.3237,0.0523 L0.3277,0.0523 L0.3309,0.0523 Q0.3398,0.0523,0.3398,0.0556 Q0.3398,0.0581,0.3341,0.0587 T0.3205,0.0593 T0.3092,0.0603 T0.3036,0.0643 T0.2932,0.068 Q0.2884,0.0684,0.2843,0.0684 Q0.2811,0.0684,0.2763,0.0678 T0.2667,0.0672 Q0.2586,0.0672,0.2422,0.0697 T0.2177,0.0747 Q0.2137,0.0759,0.1831,0.0767 T0.1494,0.0784 L0.1474,0.0784 T0.1398,0.078 T0.1285,0.0776 Q0.1173,0.0776,0.1084,0.08 Q0.1004,0.0817,0.1004,0.0846 Q0.1004,0.0867,0.104,0.0888 T0.1124,0.0923 T0.1213,0.0948 T0.1269,0.0958 T0.155,0.1014 T0.1863,0.107 Q0.2016,0.107,0.2185,0.1107 Q0.2217,0.1116,0.2257,0.1116 Q0.2289,0.1116,0.2365,0.1099 T0.2482,0.1074 L0.2538,0.1074 Q0.2683,0.1074,0.2912,0.1107 T0.3197,0.1182 Q0.3245,0.1215,0.3301,0.1215 Q0.3333,0.1215,0.3454,0.1184 T0.3687,0.1145 Q0.3735,0.1141,0.3779,0.1139 T0.3843,0.1134 T0.3871,0.1132 Q0.3896,0.1132,0.39,0.1136 T0.392,0.1161 T0.3968,0.1203 Q0.404,0.1248,0.408,0.1248 Q0.4096,0.1248,0.4165,0.1234 T0.4305,0.1215 L0.4337,0.1215 Q0.4442,0.1215,0.4518,0.1251 T0.4659,0.1294 Q0.4876,0.1319,0.5116,0.1319 T0.5622,0.141 Q0.5703,0.1439,0.5751,0.1439 Q0.5791,0.1439,0.5831,0.1423 T0.5888,0.1406 Q0.592,0.1406,0.5944,0.1412 T0.6012,0.1427 T0.6129,0.1435 Q0.6313,0.1435,0.649,0.1497 Q0.6602,0.1535,0.6667,0.1535 Q0.6715,0.1535,0.6779,0.1522 Q0.6787,0.1518,0.6795,0.1518 Q0.6827,0.1518,0.6847,0.1541 T0.6867,0.1593 Q0.6867,0.1618,0.6851,0.1634 Q0.6835,0.1655,0.6775,0.1667 T0.6598,0.169 T0.6458,0.1705 T0.6402,0.1709 Q0.6378,0.1709,0.6321,0.1705 T0.6225,0.1701 Q0.6096,0.1701,0.5968,0.1738 Q0.5823,0.1779,0.5446,0.1779 Q0.5205,0.1779,0.5116,0.1767 Q0.51,0.1763,0.5084,0.1763 Q0.5028,0.1763,0.4944,0.1796 T0.4835,0.1829 L0.4771,0.1833 T0.4659,0.1844 L0.4546,0.1858 T0.443,0.1881 T0.4337,0.1912 Q0.4321,0.1916,0.4305,0.1916 Q0.4273,0.1916,0.4209,0.1895 T0.412,0.1875 Q0.408,0.1875,0.4056,0.19 Q0.4024,0.1925,0.388,0.1925 Q0.3663,0.1925,0.3558,0.1879 Q0.3518,0.1862,0.3462,0.1862 Q0.3373,0.1862,0.3213,0.191 T0.3012,0.1962 Q0.2964,0.1966,0.2799,0.1976 T0.2494,0.1997 T0.2313,0.2024 L0.2313,0.2024 L0.2293,0.2024 L0.2261,0.2024 L0.2217,0.2024 Q0.204,0.2024,0.1851,0.2047 T0.1526,0.2136 Q0.1406,0.2198,0.1406,0.226 Q0.1406,0.2319,0.1518,0.2362 T0.1719,0.2406 Q0.1791,0.2406,0.2466,0.2497 Q0.2498,0.2501,0.2574,0.2516 T0.2711,0.2538 T0.2819,0.2547 L0.2851,0.2547 Q0.2851,0.2543,0.2859,0.2543 Q0.294,0.2543,0.3096,0.2592 T0.3285,0.2684 Q0.3317,0.2721,0.3373,0.2721 Q0.3406,0.2721,0.3462,0.2708 T0.3542,0.2696 Q0.3558,0.2696,0.3574,0.27 Q0.3679,0.2737,0.3968,0.2762 Q0.3984,0.2766,0.4004,0.2779 L0.4044,0.2804 T0.4096,0.2816 Q0.412,0.2816,0.4145,0.2808 Q0.4177,0.2804,0.4249,0.2804 Q0.4586,0.2804,0.4763,0.287 Q0.4867,0.2912,0.4972,0.2912 Q0.5004,0.2912,0.5064,0.2908 T0.5165,0.2903 Q0.5293,0.2903,0.5361,0.2922 T0.547,0.3003 L0.547,0.3011 Q0.547,0.3036,0.5414,0.3049 T0.5249,0.3067 T0.51,0.3082 Q0.5092,0.3082,0.4831,0.3111 T0.4498,0.3161 Q0.408,0.3264,0.3952,0.3264 Q0.3896,0.3264,0.3863,0.3252 Q0.3783,0.3223,0.3703,0.3223 Q0.359,0.3223,0.3494,0.3277 Q0.347,0.3293,0.343,0.3293 Q0.3414,0.3293,0.3353,0.3285 T0.3253,0.3277 T0.3181,0.3285 Q0.3149,0.3289,0.2952,0.3306 T0.2699,0.3347 Q0.2675,0.3364,0.2598,0.3372 T0.2434,0.3395 T0.2297,0.3426 L0.2205,0.3461 T0.2048,0.3523 T0.1908,0.3606 T0.1791,0.3725 Q0.1743,0.3803,0.1743,0.3845 Q0.1743,0.3874,0.1763,0.3901 T0.1827,0.3976 T0.1912,0.4073 T0.198,0.4189 T0.2032,0.4276 T0.2137,0.4326 T0.2386,0.438 Q0.2458,0.4392,0.2562,0.4392 L0.2639,0.4392 L0.2699,0.4392 Q0.2795,0.4392,0.2843,0.4405 Q0.2932,0.443,0.3092,0.443 L0.3157,0.443 L0.3213,0.443 Q0.3293,0.443,0.3458,0.4463 T0.3671,0.4525 Q0.3598,0.4558,0.3373,0.4571 Q0.3124,0.4587,0.298,0.4637 Q0.294,0.465,0.2811,0.465 L0.2703,0.465 L0.2586,0.465 Q0.241,0.465,0.2305,0.4666 Q0.2185,0.4687,0.2185,0.4728 Q0.2185,0.4745,0.2205,0.4764 T0.2241,0.4791 L0.2265,0.4803 L0.2273,0.4803 Q0.2506,0.4824,0.261,0.4824 Q0.2675,0.4824,0.2699,0.482 Q0.2699,0.4815,0.2707,0.4815 Q0.2771,0.4815,0.3124,0.4898 Q0.3173,0.4907,0.3229,0.4913 T0.3386,0.4925 T0.3542,0.4936 T0.3663,0.494 Q0.3711,0.494,0.3787,0.4936 T0.3888,0.4932 Q0.392,0.4932,0.3944,0.4936 T0.4032,0.4959 T0.4181,0.499 T0.4369,0.5006 Q0.449,0.501,0.455,0.5021 T0.4639,0.5048 T0.4675,0.5093 L0.4675,0.5097 Q0.4675,0.5106,0.4663,0.511 T0.4622,0.5116 L0.4566,0.512 T0.4486,0.5124 T0.4402,0.5127 Q0.4345,0.5131,0.4257,0.5153 T0.4096,0.5176 Q0.4064,0.5176,0.4024,0.5172 L0.3968,0.5172 Q0.3863,0.5172,0.3731,0.522 T0.359,0.5268 Q0.355,0.528,0.3317,0.5321 T0.3004,0.5388 Q0.2867,0.5433,0.2337,0.5479 Q0.2096,0.55,0.2096,0.5562 Q0.2096,0.5591,0.2145,0.5628 Q0.2193,0.5662,0.2289,0.5662 Q0.2313,0.5662,0.2373,0.5657 T0.2458,0.5653 L0.2498,0.5653 Q0.2635,0.5674,0.3124,0.5674 Q0.3261,0.5674,0.3301,0.567 L0.3317,0.567 Q0.3341,0.567,0.3394,0.568 T0.3486,0.5691 Q0.3542,0.5691,0.3606,0.5678 T0.3699,0.5666 T0.3759,0.5686 T0.3839,0.5718 T0.3908,0.573 T0.3964,0.5734 T0.4048,0.5742 T0.4165,0.5751 T0.4277,0.5757 T0.4369,0.5778 Q0.4426,0.5794,0.4538,0.5794 Q0.4586,0.5794,0.4679,0.5792 T0.4811,0.579 Q0.4924,0.579,0.4956,0.5807 Q0.502,0.584,0.5108,0.584 L0.5137,0.584 T0.5173,0.5838 L0.5205,0.5834 L0.5237,0.583 L0.5269,0.5825 T0.5305,0.5821 T0.5341,0.5819 L0.5365,0.5819 Q0.5414,0.5819,0.5562,0.5842 T0.5855,0.5865 Q0.5912,0.5865,0.5948,0.5867 T0.6012,0.5869 T0.6064,0.5873 T0.61,0.5877 T0.6133,0.5886 T0.6157,0.5896 L0.6185,0.591 T0.6217,0.5931 Q0.6265,0.5952,0.6369,0.5952 L0.6418,0.5952 L0.6466,0.5952 Q0.6723,0.5952,0.6932,0.606 Q0.698,0.608,0.7044,0.608 Q0.7084,0.608,0.7161,0.6068 T0.7261,0.6056 Q0.7309,0.6056,0.7454,0.6051 T0.7647,0.6047 Q0.7775,0.6047,0.8072,0.6101 Q0.8096,0.6105,0.812,0.6105 Q0.8177,0.6105,0.8297,0.6085 T0.8474,0.606 L0.8506,0.606 Q0.8562,0.606,0.8647,0.607 T0.8831,0.6095 T0.8964,0.6118 Q0.9036,0.6122,0.9221,0.6122 L0.9406,0.6122 L0.959,0.6122 L0.9767,0.6122 Q0.9952,0.613,0.9952,0.6172 Q0.9952,0.6176,0.9944,0.6221 Q0.9936,0.6259,0.9574,0.6259 L0.955,0.6259 L0.9526,0.6259 Q0.9478,0.6259,0.9373,0.628 T0.9205,0.63 L0.9173,0.63 Q0.9133,0.6296,0.9084,0.6296 Q0.9004,0.6296,0.8715,0.6321 Q0.8233,0.6367,0.7944,0.64 Q0.7888,0.6408,0.7839,0.6408 T0.7739,0.6402 T0.7671,0.6396 Q0.7639,0.6396,0.7622,0.64 Q0.7614,0.6404,0.759,0.6404 Q0.7574,0.6404,0.749,0.6398 T0.7357,0.6392 Q0.7261,0.6392,0.7213,0.6412 Q0.7173,0.6425,0.7032,0.6437 T0.6711,0.6458 T0.6474,0.647 Q0.645,0.6474,0.6418,0.6474 Q0.6353,0.6474,0.6277,0.6454 T0.6161,0.6433 Q0.6104,0.6433,0.6072,0.6456 T0.6,0.6479 Q0.5912,0.6479,0.5486,0.6557 Q0.5422,0.657,0.5373,0.657 Q0.5349,0.657,0.5289,0.6564 T0.5173,0.6557 L0.5124,0.6557 Q0.4996,0.6562,0.4739,0.6611 Q0.4675,0.6624,0.4398,0.6645 T0.3851,0.668 T0.3558,0.6698 Q0.355,0.6698,0.3285,0.6721 T0.2948,0.6752 Q0.2892,0.6761,0.2843,0.6786 T0.2747,0.6831 T0.2618,0.6852 Q0.253,0.6852,0.2394,0.6893 T0.2141,0.6993 T0.1968,0.7105 Q0.1944,0.713,0.1944,0.7155 Q0.1944,0.7196,0.2012,0.7229 T0.2177,0.7285 T0.2281,0.7308 Q0.2321,0.7329,0.2365,0.7339 T0.245,0.7356 T0.2514,0.7366 Q0.2522,0.737,0.2554,0.7379 T0.2614,0.7393 T0.2679,0.7406 T0.2755,0.7416 T0.2835,0.742 Q0.29,0.742,0.3048,0.7428 T0.3253,0.7437 L0.3301,0.7437 L0.3333,0.7437 Q0.3341,0.7437,0.3598,0.7466 Q0.3639,0.7474,0.3671,0.7474 T0.3759,0.7466 T0.3855,0.7457 Q0.388,0.7457,0.3904,0.7462 Q0.3984,0.7466,0.4137,0.7495 T0.4349,0.7532 T0.441,0.7557 Q0.441,0.7599,0.4289,0.7619 Q0.4257,0.7628,0.4197,0.7634 T0.4072,0.7644 T0.3984,0.7652 Q0.3968,0.7652,0.3912,0.7655 T0.3811,0.7665 T0.3719,0.7694 Q0.3695,0.7702,0.3655,0.7702 T0.3546,0.7686 T0.3462,0.7665 L0.3446,0.7665 Q0.3406,0.7665,0.3293,0.769 T0.3149,0.7715 Q0.3084,0.7715,0.2952,0.7748 T0.2763,0.781 Q0.2675,0.7847,0.2554,0.7864 Q0.253,0.7864,0.2325,0.7874 T0.2024,0.7893 Q0.2008,0.7893,0.1984,0.7897 T0.1948,0.7901 T0.1916,0.7903 T0.1888,0.7908 T0.1867,0.7914 T0.1847,0.7922 L0.1831,0.793 T0.1819,0.7943 L0.1811,0.7959 T0.1799,0.7976 Q0.1791,0.7993,0.1791,0.8001 Q0.1791,0.8034,0.1831,0.8051 T0.1932,0.8078 T0.2,0.8092 Q0.2072,0.8117,0.2257,0.8117 Q0.2337,0.8117,0.2586,0.8109 L0.2635,0.8109 Q0.2707,0.8109,0.2767,0.8117 T0.2859,0.8136 T0.2936,0.8163 T0.3012,0.8187 Q0.3068,0.82,0.3277,0.8206 T0.3526,0.8237 Q0.3582,0.8266,0.3622,0.8266 Q0.3647,0.8266,0.3687,0.8258 T0.3751,0.825 Q0.396,0.825,0.404,0.8291 Q0.408,0.8316,0.4161,0.8316 Q0.4217,0.8316,0.4353,0.8295 L0.4394,0.8295 Q0.4466,0.8295,0.4659,0.8366 Q0.4699,0.8378,0.4747,0.8378 Q0.4763,0.8378,0.4807,0.8374 T0.4884,0.837 Q0.494,0.837,0.5004,0.8382 Q0.5052,0.8391,0.5124,0.8399 Q0.5221,0.8411,0.5249,0.8422 T0.5277,0.8461 Q0.5277,0.8482,0.5233,0.849 T0.5112,0.8501 T0.4996,0.8511 Q0.4964,0.8519,0.4851,0.8523 T0.4659,0.8544 Q0.4578,0.8557,0.4402,0.8557 L0.4349,0.8557 L0.4297,0.8557 L0.4209,0.8557 Q0.408,0.8565,0.3867,0.8571 T0.3614,0.8582 Q0.3317,0.8611,0.3237,0.8611 L0.3225,0.8611 L0.3213,0.8611 Q0.2972,0.8611,0.2972,0.8681 Q0.2972,0.8714,0.3008,0.8747 T0.3044,0.8805 Q0.3044,0.881,0.3036,0.8814 Q0.3012,0.8839,0.2851,0.8839 Q0.2779,0.8839,0.2739,0.883 Q0.2723,0.8826,0.2699,0.8826 T0.2643,0.8835 T0.259,0.8853 T0.2542,0.8876 T0.249,0.8893 Q0.2386,0.8922,0.2056,0.8946 Q0.2016,0.8951,0.1916,0.898 T0.1703,0.9009 Q0.1518,0.9009,0.1337,0.9044 T0.1149,0.9146 L0.1149,0.9158 Q0.1149,0.9175,0.1157,0.9187 T0.1173,0.921 T0.1201,0.9226 T0.1233,0.9237 T0.1269,0.9245 L0.1305,0.9251 T0.1337,0.9255 L0.1357,0.9258 Q0.139,0.9262,0.1446,0.9262 T0.1542,0.9266 T0.1598,0.9282 Q0.1614,0.9303,0.1647,0.9314 T0.1739,0.9328 T0.1815,0.9336 Q0.1863,0.9345,0.1976,0.9372 T0.2145,0.9415 Q0.2225,0.9432,0.2285,0.9467 T0.2378,0.9515 Q0.2426,0.9531,0.2514,0.9531 Q0.2538,0.9531,0.2602,0.9529 T0.2707,0.9527 Q0.2763,0.9527,0.2815,0.9531 T0.2867,0.9569 T0.2811,0.9614 Q0.2434,0.9627,0.2329,0.9652 Q0.2249,0.9672,0.2241,0.9672 L0.2096,0.9668 L0.2056,0.9668 Q0.2,0.9668,0.196,0.9672 T0.1884,0.9687 T0.1815,0.9708 T0.1731,0.9737 T0.1639,0.9772 Q0.1518,0.9805,0.1285,0.9805 L0.1253,0.9805 Q0.1124,0.9805,0.1076,0.9818 Q0.1012,0.983,0.0831,0.9876 T0.0594,0.9934 Q0.049,0.9954,0.0064,0.9954 Z M0.0032,0.9979 L0.0024,0.0037 Q0.0161,0.0021,0.0289,0.0062 Q0.0337,0.0075,0.0671,0.0066 T0.1116,0.01 T0.1337,0.0166 T0.151,0.022 Q0.1582,0.0249,0.1659,0.0251 T0.1767,0.0265 Q0.1807,0.0278,0.2,0.0292 T0.2265,0.0328 Q0.2305,0.034,0.2329,0.034 T0.239,0.0326 T0.2458,0.0311 T0.253,0.0319 Q0.2578,0.0332,0.2614,0.0342 T0.2667,0.0363 T0.2723,0.0384 T0.2827,0.0406 Q0.3084,0.0448,0.3141,0.0485 Q0.3149,0.0494,0.3225,0.0494 T0.3361,0.0504 T0.3422,0.0554 T0.3357,0.0606 T0.3205,0.0618 T0.31,0.0626 Q0.3092,0.0626,0.3056,0.0659 T0.2932,0.0701 Q0.2884,0.0709,0.2843,0.0709 Q0.2811,0.0709,0.2759,0.0701 T0.2667,0.0693 Q0.2586,0.0693,0.2422,0.0718 T0.2193,0.0763 Q0.2137,0.0784,0.1831,0.079 T0.1502,0.0805 Q0.1494,0.0809,0.147,0.0809 Q0.1454,0.0809,0.1386,0.0803 T0.1277,0.0796 Q0.1173,0.0796,0.1092,0.0821 Q0.1036,0.0834,0.1036,0.0854 Q0.1036,0.0879,0.1129,0.0908 T0.1269,0.0937 Q0.1293,0.0937,0.1558,0.0991 T0.1863,0.1045 Q0.2016,0.1045,0.2193,0.1087 Q0.2225,0.1091,0.2249,0.1091 Q0.2289,0.1091,0.2357,0.1076 T0.2482,0.1054 Q0.2506,0.1049,0.2546,0.1049 Q0.2699,0.1049,0.2924,0.1085 T0.3213,0.1165 Q0.3253,0.119,0.3301,0.119 Q0.3325,0.119,0.3446,0.1161 T0.3687,0.1124 Q0.3831,0.1112,0.3863,0.1112 T0.3908,0.1118 T0.3936,0.1145 T0.3984,0.1186 Q0.4048,0.1228,0.408,0.1228 Q0.4096,0.1228,0.4161,0.1213 T0.4305,0.1195 L0.4337,0.1195 Q0.4426,0.1195,0.4474,0.1211 T0.4566,0.1246 T0.4667,0.1273 Q0.4876,0.1298,0.5116,0.1298 Q0.5365,0.1298,0.5631,0.1389 Q0.5703,0.1414,0.5751,0.1414 Q0.5783,0.1414,0.5819,0.1398 T0.5888,0.1381 Q0.5928,0.1381,0.5984,0.1398 T0.6129,0.1414 Q0.6321,0.1414,0.6498,0.1477 Q0.661,0.1514,0.6667,0.1514 Q0.6707,0.1514,0.6771,0.1501 Q0.6787,0.1497,0.6795,0.1497 Q0.6835,0.1497,0.6863,0.1526 T0.6892,0.1593 Q0.6892,0.1626,0.6876,0.1647 Q0.6851,0.1672,0.6799,0.1686 T0.6695,0.1705 T0.6566,0.1715 T0.6458,0.1725 Q0.6434,0.173,0.6402,0.173 Q0.6378,0.173,0.6317,0.1727 T0.6225,0.1725 Q0.6096,0.1725,0.5984,0.1759 Q0.5823,0.1804,0.5454,0.1804 Q0.5205,0.1804,0.5116,0.1788 L0.5084,0.1788 Q0.5036,0.1788,0.4956,0.1819 T0.4843,0.185 Q0.4434,0.1887,0.4353,0.1929 Q0.4321,0.1945,0.4289,0.1945 T0.4197,0.1922 T0.4112,0.19 T0.4072,0.1912 Q0.4032,0.1949,0.388,0.1949 Q0.3663,0.1949,0.3542,0.19 Q0.351,0.1883,0.3462,0.1883 Q0.3406,0.1883,0.3321,0.1906 T0.3153,0.1954 T0.302,0.1983 Q0.2972,0.1991,0.2859,0.1997 T0.2643,0.201 T0.245,0.2024 T0.2337,0.2041 Q0.2321,0.2045,0.2181,0.2045 T0.1851,0.207 T0.1542,0.2157 Q0.143,0.2211,0.143,0.226 Q0.143,0.2314,0.153,0.2348 T0.1719,0.2381 Q0.1791,0.2381,0.2474,0.2476 Q0.2506,0.248,0.2558,0.2489 T0.2651,0.2505 T0.2739,0.252 T0.2819,0.2526 Q0.2835,0.2526,0.2851,0.2522 L0.2859,0.2522 Q0.2948,0.2522,0.3104,0.2574 T0.3309,0.2671 Q0.3333,0.2696,0.3365,0.2696 Q0.339,0.2696,0.3442,0.2684 T0.3526,0.2671 T0.3582,0.2679 Q0.3687,0.2717,0.3968,0.2742 Q0.4,0.2746,0.4036,0.2771 T0.4096,0.2796 Q0.4112,0.2796,0.4137,0.2787 Q0.4169,0.2779,0.4257,0.2779 Q0.4594,0.2779,0.4779,0.2849 Q0.4876,0.2891,0.4972,0.2891 Q0.4996,0.2891,0.506,0.2887 T0.5165,0.2883 Q0.5253,0.2883,0.5305,0.2889 T0.541,0.292 T0.5494,0.299 L0.5494,0.3015 Q0.5494,0.3036,0.5466,0.3051 T0.5386,0.3073 T0.5285,0.3086 T0.5181,0.3094 T0.5108,0.3102 Q0.5068,0.3107,0.494,0.3121 L0.4683,0.315 T0.4506,0.3181 Q0.449,0.3181,0.4426,0.32 T0.4313,0.3229 T0.4197,0.3256 T0.4064,0.3281 T0.3952,0.3289 Q0.3888,0.3289,0.3855,0.3273 Q0.3783,0.3243,0.3703,0.3243 Q0.3598,0.3243,0.351,0.3297 Q0.3478,0.3314,0.3422,0.3314 Q0.3398,0.3314,0.3337,0.3306 T0.3245,0.3297 T0.3189,0.3306 Q0.3149,0.3314,0.2956,0.3328 T0.2715,0.3368 Q0.2691,0.338,0.2635,0.3391 T0.2522,0.3407 T0.2406,0.3424 T0.2313,0.3447 Q0.2305,0.3447,0.2213,0.348 T0.206,0.354 T0.1924,0.3621 T0.1815,0.3733 Q0.1767,0.3816,0.1767,0.3849 Q0.1767,0.387,0.1783,0.3893 T0.1847,0.3961 T0.1936,0.4063 T0.2004,0.4181 T0.2052,0.4264 T0.2145,0.4309 T0.2386,0.4359 Q0.2458,0.4372,0.2554,0.4372 Q0.2578,0.4372,0.2627,0.437 T0.2699,0.4367 Q0.2795,0.4367,0.2851,0.4384 Q0.294,0.4409,0.3092,0.4409 Q0.3116,0.4409,0.3157,0.4407 T0.3213,0.4405 Q0.3301,0.4405,0.3466,0.444 T0.3687,0.4509 Q0.3703,0.4517,0.3703,0.4525 Q0.3703,0.4575,0.3373,0.4596 Q0.3133,0.4608,0.2988,0.4658 Q0.294,0.4674,0.2803,0.4674 Q0.2763,0.4674,0.2687,0.4672 T0.257,0.467 Q0.2402,0.467,0.2313,0.4687 Q0.2209,0.4708,0.2209,0.4737 Q0.2209,0.4741,0.2213,0.4749 T0.2229,0.4764 L0.2249,0.4774 T0.2265,0.4778 L0.2273,0.4782 Q0.2498,0.4803,0.2618,0.4803 Q0.2667,0.4803,0.2691,0.4799 Q0.2699,0.4795,0.2715,0.4795 Q0.2779,0.4795,0.3133,0.4873 Q0.3181,0.4886,0.3233,0.4892 T0.3386,0.4905 T0.3542,0.4915 T0.3655,0.4919 Q0.3703,0.4919,0.3779,0.4915 T0.3888,0.4911 T0.3952,0.4915 Q0.3976,0.4919,0.4008,0.4927 L0.4072,0.4944 T0.4149,0.4961 T0.4249,0.4975 T0.4369,0.4985 Q0.449,0.499,0.4554,0.5 T0.4651,0.5031 T0.4699,0.5085 Q0.4707,0.5093,0.4707,0.5097 Q0.4707,0.5114,0.4687,0.5124 T0.4631,0.5139 T0.4562,0.5145 T0.4478,0.5147 L0.4402,0.5147 Q0.4353,0.5151,0.4265,0.5174 T0.4096,0.5197 L0.4024,0.5197 Q0.4,0.5193,0.3968,0.5193 Q0.3896,0.5193,0.3831,0.5209 T0.3703,0.5253 T0.3598,0.5288 Q0.3558,0.5301,0.3321,0.5342 T0.302,0.5409 Q0.2876,0.5454,0.2337,0.55 Q0.2112,0.5521,0.2112,0.5558 Q0.2112,0.5574,0.2161,0.5612 Q0.2201,0.5637,0.2281,0.5637 Q0.2305,0.5637,0.2361,0.5633 T0.2442,0.5628 Q0.2474,0.5628,0.2498,0.5633 Q0.2635,0.5653,0.3133,0.5653 Q0.3261,0.5653,0.3301,0.5649 L0.3325,0.5649 Q0.3349,0.5649,0.3402,0.5657 T0.3494,0.5666 Q0.3542,0.5666,0.3598,0.5657 Q0.3663,0.5645,0.3695,0.5645 Q0.3735,0.5645,0.3775,0.5666 T0.3847,0.5695 Q0.3888,0.5707,0.3912,0.5709 T0.3968,0.5711 T0.4048,0.572 Q0.4096,0.5724,0.4165,0.5726 T0.4281,0.5734 T0.4378,0.5757 Q0.4418,0.5774,0.4522,0.5774 Q0.4562,0.5774,0.4655,0.5769 T0.4795,0.5765 Q0.4932,0.5765,0.4972,0.579 Q0.5028,0.5819,0.51,0.5819 Q0.5141,0.5819,0.5221,0.5809 T0.5341,0.5798 L0.5373,0.5798 Q0.5422,0.5798,0.557,0.5821 T0.5855,0.5844 L0.5936,0.5844 L0.5992,0.5844 T0.604,0.5848 T0.6076,0.5852 T0.6108,0.5859 T0.6137,0.5865 T0.6161,0.5873 T0.6181,0.5881 T0.6209,0.5896 L0.6233,0.591 Q0.6265,0.5927,0.6353,0.5927 L0.6402,0.5927 L0.645,0.5927 Q0.6723,0.5927,0.6948,0.6039 Q0.6988,0.606,0.7036,0.606 Q0.7076,0.606,0.7153,0.6045 T0.7261,0.6031 Q0.7309,0.6031,0.7454,0.6027 T0.7647,0.6022 Q0.7783,0.6022,0.808,0.608 L0.812,0.608 Q0.8169,0.608,0.8293,0.6062 T0.8474,0.6039 L0.8506,0.6039 Q0.857,0.6039,0.8655,0.6049 T0.8835,0.6074 T0.8972,0.6093 Q0.9036,0.6101,0.9213,0.6101 L0.9398,0.6101 L0.959,0.6101 L0.9767,0.6101 Q0.9976,0.6109,0.9976,0.6176 Q0.9976,0.6188,0.9968,0.6226 Q0.9952,0.6284,0.9671,0.6284 L0.959,0.6284 L0.9526,0.6284 Q0.9486,0.6284,0.9382,0.6302 T0.9205,0.6321 L0.9173,0.6321 Q0.9133,0.6317,0.9092,0.6317 Q0.9012,0.6317,0.8715,0.6346 Q0.8233,0.6387,0.7944,0.6421 Q0.7888,0.6429,0.7831,0.6429 Q0.7783,0.6429,0.7731,0.6425 T0.7663,0.6421 L0.7622,0.6421 Q0.7614,0.6425,0.7586,0.6425 T0.7474,0.6421 T0.7349,0.6416 Q0.7261,0.6416,0.7221,0.6429 Q0.7173,0.645,0.7032,0.646 T0.6711,0.6479 T0.6482,0.6491 Q0.645,0.6495,0.6418,0.6495 Q0.6353,0.6495,0.6273,0.6474 T0.6161,0.6454 Q0.612,0.6454,0.6088,0.6477 T0.6,0.6499 Q0.5912,0.6499,0.5494,0.6578 Q0.5422,0.6591,0.5373,0.6591 Q0.5341,0.6591,0.5281,0.6584 T0.5173,0.6578 L0.5124,0.6578 Q0.5004,0.6582,0.4747,0.6632 Q0.4675,0.6649,0.4398,0.6667 T0.3851,0.6703 T0.3566,0.6719 Q0.3534,0.6723,0.3406,0.6734 T0.3141,0.6757 T0.2948,0.6773 Q0.2908,0.6781,0.2819,0.6829 T0.2618,0.6877 Q0.2482,0.6877,0.2273,0.6958 T0.1992,0.7117 Q0.1976,0.7138,0.1976,0.7155 Q0.1976,0.7188,0.2032,0.7213 T0.2181,0.7258 T0.2289,0.7292 Q0.2337,0.7308,0.2378,0.7316 L0.2458,0.7333 T0.2522,0.7345 Q0.2747,0.7399,0.2835,0.7399 Q0.29,0.7399,0.3048,0.7408 T0.3253,0.7416 Q0.3277,0.7416,0.3297,0.7414 T0.3341,0.7412 L0.3598,0.7445 Q0.3639,0.7449,0.3663,0.7449 Q0.3695,0.7449,0.3751,0.7443 T0.3855,0.7437 L0.3904,0.7437 Q0.3984,0.7441,0.4141,0.7472 T0.4353,0.7507 Q0.4434,0.752,0.4434,0.7553 Q0.4434,0.7578,0.439,0.7605 T0.4297,0.764 Q0.4257,0.7648,0.4201,0.7655 T0.4076,0.7665 T0.3984,0.7673 Q0.3968,0.7673,0.3912,0.7677 T0.3815,0.7688 T0.3735,0.771 T0.3655,0.7727 Q0.3606,0.7727,0.3534,0.7708 T0.3458,0.769 L0.3446,0.769 Q0.3414,0.769,0.3297,0.7713 T0.3149,0.7735 Q0.3092,0.7735,0.296,0.7771 T0.2771,0.7831 Q0.2683,0.7872,0.2554,0.7885 Q0.253,0.7889,0.2325,0.7897 T0.2032,0.7914 Q0.2008,0.7918,0.198,0.792 T0.194,0.7924 T0.1912,0.7928 T0.1888,0.793 T0.1867,0.7934 T0.1851,0.7943 T0.1843,0.7953 T0.1835,0.7968 T0.1823,0.7984 T0.1815,0.8001 Q0.1815,0.8022,0.1847,0.8032 T0.1932,0.8053 T0.2008,0.8071 Q0.2072,0.8092,0.2265,0.8092 Q0.2361,0.8092,0.2586,0.8088 L0.2635,0.8088 Q0.2731,0.8088,0.2795,0.8098 T0.2928,0.8134 T0.302,0.8167 Q0.306,0.8179,0.3277,0.8183 T0.3542,0.8221 Q0.359,0.8246,0.3622,0.8246 Q0.3639,0.8246,0.3675,0.8237 T0.3751,0.8229 Q0.3968,0.8229,0.4048,0.8275 Q0.4088,0.8291,0.4161,0.8291 Q0.4225,0.8291,0.4353,0.8275 Q0.4369,0.827,0.4394,0.827 Q0.4474,0.827,0.4675,0.8345 Q0.4707,0.8358,0.4739,0.8358 Q0.4755,0.8358,0.4799,0.8353 T0.4876,0.8349 Q0.494,0.8349,0.5012,0.8362 L0.5076,0.837 T0.5133,0.8376 T0.5181,0.8382 T0.5221,0.8391 L0.5253,0.8399 T0.5277,0.8409 L0.5293,0.8422 T0.5301,0.8438 L0.5301,0.8461 Q0.5301,0.8499,0.5249,0.8511 T0.5112,0.8526 T0.5004,0.8532 Q0.4964,0.854,0.4851,0.8546 T0.4667,0.8565 Q0.4578,0.8577,0.441,0.8577 L0.4345,0.8577 L0.4289,0.8577 L0.4209,0.8577 Q0.4153,0.8582,0.4088,0.8586 T0.3968,0.8592 T0.3855,0.8594 T0.3755,0.8596 T0.3675,0.86 T0.3622,0.8602 Q0.3317,0.8635,0.3237,0.8635 Q0.2996,0.8635,0.2996,0.8681 Q0.2996,0.8698,0.3032,0.8733 T0.3068,0.8797 L0.3068,0.8822 Q0.3052,0.8859,0.2859,0.8859 Q0.2787,0.8859,0.2739,0.8851 L0.2699,0.8851 Q0.2659,0.8851,0.259,0.888 T0.2498,0.8913 Q0.2394,0.8946,0.2056,0.8971 Q0.2032,0.8971,0.1928,0.9002 T0.1703,0.9034 Q0.1526,0.9034,0.1353,0.9067 T0.1173,0.915 L0.1173,0.9158 Q0.1173,0.9166,0.1177,0.9175 T0.1189,0.9191 T0.1205,0.9204 T0.1229,0.9212 T0.1257,0.922 T0.1285,0.9226 T0.1309,0.9231 T0.1337,0.9235 T0.1365,0.9237 L0.1446,0.9237 T0.1554,0.9243 T0.1614,0.9268 T0.1659,0.9293 T0.1743,0.9303 T0.1823,0.9316 Q0.1863,0.932,0.1912,0.9332 T0.2028,0.9361 T0.2153,0.939 Q0.2233,0.9411,0.2297,0.9448 T0.2386,0.9494 Q0.2426,0.9506,0.2506,0.9506 Q0.253,0.9506,0.2594,0.9504 T0.2699,0.9502 Q0.2763,0.9502,0.2819,0.9511 Q0.2851,0.9511,0.2871,0.9529 T0.2892,0.9573 Q0.2892,0.9627,0.2811,0.9635 Q0.2434,0.9652,0.2337,0.9672 Q0.2257,0.9693,0.2241,0.9693 Q0.2217,0.9693,0.2096,0.9689 L0.2056,0.9689 Q0.1976,0.9689,0.1924,0.9699 T0.1787,0.9741 L0.1647,0.9793 Q0.1518,0.983,0.1285,0.983 L0.1261,0.983 L0.1237,0.983 Q0.1124,0.983,0.1076,0.9838 Q0.102,0.9851,0.0839,0.9896 T0.0602,0.9954 T0.0406,0.9971 T0.0153,0.9979 L0.0032,0.9979 Z', type: CustomGlyphVectorType.FILL } },\n  // '\\u{E0C8}' is unused\n  '\\u{E0CA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.9992,0.998 Q0.9992,1,0.9968,1 Q0.9521,1,0.9392,0.998 Q0.9335,0.997,0.9152,0.9921 T0.8913,0.9862 Q0.8873,0.9852,0.8783,0.9852 L0.8747,0.9852 L0.871,0.9852 Q0.8483,0.9852,0.8345,0.9812 Q0.8289,0.9792,0.8228,0.9773 T0.8135,0.9743 T0.8054,0.9723 T0.7948,0.9713 L0.7908,0.9713 L0.777,0.9713 T0.7656,0.9693 Q0.7616,0.9683,0.7498,0.9674 T0.7283,0.9664 L0.7186,0.9654 Q0.708,0.9644,0.708,0.9575 Q0.708,0.9545,0.7109,0.952 T0.7178,0.9486 L0.7315,0.9486 L0.7417,0.9486 L0.7502,0.9486 Q0.7575,0.9486,0.7599,0.9476 Q0.7616,0.9466,0.7685,0.9426 T0.7843,0.9377 Q0.7899,0.9357,0.8009,0.9332 T0.8175,0.9298 Q0.82,0.9288,0.8228,0.9283 T0.8277,0.9278 T0.8313,0.9273 T0.8341,0.9263 T0.8362,0.9258 Q0.8402,0.9219,0.854,0.9219 L0.8569,0.9219 L0.8597,0.9219 L0.8637,0.9219 Q0.88,0.9199,0.88,0.9159 L0.88,0.9149 Q0.88,0.913,0.8719,0.911 T0.852,0.9075 T0.8297,0.906 Q0.8224,0.906,0.8167,0.905 T0.8074,0.9031 T0.8001,0.9006 T0.794,0.8991 Q0.7599,0.8971,0.7494,0.8942 Q0.7461,0.8932,0.7397,0.8902 T0.7299,0.8872 L0.7267,0.8872 Q0.721,0.8882,0.7121,0.8882 Q0.6926,0.8882,0.691,0.8833 L0.691,0.8803 Q0.691,0.8773,0.6922,0.8749 T0.6955,0.8704 L0.6975,0.8684 Q0.6926,0.8655,0.6764,0.8655 Q0.6683,0.8655,0.6375,0.8625 Q0.6358,0.8625,0.629,0.862 T0.6148,0.8615 T0.5973,0.861 T0.5791,0.8605 L0.5718,0.8605 L0.5661,0.8605 L0.5596,0.8605 Q0.5426,0.8605,0.5337,0.8586 Q0.5264,0.8576,0.515,0.8571 T0.4996,0.8556 Q0.498,0.8556,0.4947,0.8551 T0.4882,0.8546 T0.4809,0.8541 T0.474,0.8531 T0.4692,0.8506 T0.4672,0.8467 Q0.4672,0.8437,0.468,0.8422 T0.4704,0.8398 T0.4736,0.8383 T0.4781,0.8368 T0.4838,0.8358 T0.4911,0.8353 T0.4988,0.8338 Q0.5069,0.8328,0.5142,0.8328 Q0.5174,0.8328,0.5215,0.8333 T0.5264,0.8338 Q0.5296,0.8338,0.532,0.8328 Q0.5523,0.8249,0.5604,0.8249 L0.5653,0.8249 Q0.5775,0.8269,0.5848,0.8269 Q0.5912,0.8269,0.5937,0.8259 Q0.6026,0.821,0.6253,0.821 Q0.6294,0.821,0.6334,0.822 T0.6383,0.8229 Q0.6399,0.8229,0.644,0.82 Q0.6504,0.817,0.6723,0.816 T0.6975,0.814 Q0.6999,0.814,0.7048,0.8121 T0.7129,0.8091 T0.7226,0.8071 T0.7372,0.8061 L0.7421,0.8061 Q0.7624,0.8071,0.7721,0.8071 Q0.7924,0.8071,0.7981,0.8051 Q0.8021,0.8032,0.807,0.8027 T0.8135,0.8017 T0.8151,0.8002 L0.8151,0.7992 Q0.8143,0.7962,0.8127,0.7957 T0.8045,0.7943 Q0.7997,0.7943,0.7964,0.7933 Q0.7883,0.7933,0.7676,0.7923 T0.7437,0.7903 Q0.7307,0.7893,0.7218,0.7844 Q0.7194,0.7834,0.7141,0.7819 L0.7036,0.7789 T0.693,0.7765 T0.6853,0.7755 Q0.6805,0.7755,0.6691,0.773 T0.6553,0.7705 Q0.6545,0.7705,0.6545,0.7715 Q0.6537,0.7715,0.65,0.7725 T0.6423,0.774 T0.635,0.7745 Q0.6294,0.7745,0.6257,0.773 T0.618,0.771 T0.6083,0.77 T0.601,0.7695 Q0.5985,0.7695,0.5921,0.769 T0.5795,0.7676 T0.5702,0.7666 Q0.5653,0.7656,0.5596,0.7616 T0.5539,0.7547 Q0.5539,0.7498,0.5645,0.7488 Q0.5693,0.7478,0.5852,0.7448 T0.6099,0.7418 L0.6148,0.7418 Q0.6196,0.7418,0.6253,0.7423 T0.6338,0.7428 L0.6399,0.7428 Q0.6431,0.7418,0.6488,0.7409 T0.6586,0.7394 T0.6659,0.7389 L0.6707,0.7389 Q0.6723,0.7399,0.6748,0.7399 Q0.6796,0.7399,0.6946,0.7389 T0.7161,0.7379 Q0.7251,0.7379,0.7478,0.7319 Q0.7494,0.7319,0.7539,0.7315 T0.762,0.73 T0.7697,0.727 T0.7818,0.723 T0.7948,0.7196 T0.7997,0.7151 Q0.7997,0.7141,0.7989,0.7132 Q0.7924,0.7062,0.7717,0.6978 T0.738,0.6894 Q0.7267,0.6894,0.717,0.6845 T0.7048,0.6795 Q0.6975,0.6785,0.6711,0.6766 T0.6431,0.6746 Q0.6415,0.6736,0.6144,0.6721 T0.5596,0.6686 T0.5247,0.6657 Q0.4996,0.6607,0.4874,0.6597 L0.4834,0.6597 Q0.4785,0.6597,0.4724,0.6607 T0.4631,0.6617 Q0.4574,0.6617,0.4501,0.6597 Q0.4088,0.6518,0.4006,0.6518 Q0.3933,0.6518,0.3901,0.6499 T0.3844,0.6479 T0.3739,0.6499 T0.3585,0.6518 L0.352,0.6518 Q0.3471,0.6508,0.335,0.6503 T0.3122,0.6494 T0.2912,0.6479 T0.2766,0.6449 Q0.2733,0.6439,0.2668,0.6439 Q0.2628,0.6439,0.2543,0.6444 T0.2425,0.6449 T0.2368,0.6439 L0.2344,0.6439 Q0.2336,0.6439,0.2283,0.6444 T0.2182,0.6449 Q0.2117,0.6449,0.2052,0.6439 Q0.1768,0.6409,0.1281,0.637 Q0.0989,0.634,0.0916,0.634 L0.0835,0.634 L0.0795,0.634 Q0.0722,0.634,0.0616,0.632 T0.0479,0.6301 Q0.0462,0.6301,0.0406,0.6306 T0.0308,0.6311 Q0.0032,0.6311,0.0008,0.6231 Q0,0.6202,0,0.6182 Q0,0.6083,0.0227,0.6083 Q0.03,0.6073,0.0422,0.6073 Q0.0487,0.6073,0.0608,0.6078 T0.0787,0.6083 Q0.0973,0.6083,0.103,0.6073 Q0.1062,0.6063,0.1131,0.6053 T0.1253,0.6039 T0.1375,0.6024 T0.1492,0.6014 L0.1533,0.6014 Q0.1598,0.6024,0.1723,0.6044 T0.189,0.6063 Q0.1906,0.6063,0.1922,0.6053 Q0.2214,0.6004,0.236,0.6004 Q0.2409,0.6004,0.2551,0.6009 T0.2741,0.6014 Q0.2782,0.6014,0.2859,0.6024 T0.2968,0.6034 Q0.3009,0.6034,0.3041,0.6024 Q0.3277,0.5905,0.356,0.5905 L0.3613,0.5905 L0.3666,0.5905 Q0.3731,0.5905,0.3755,0.5895 Q0.382,0.5856,0.3844,0.5846 T0.3938,0.5831 T0.4144,0.5826 T0.4428,0.5801 T0.4631,0.5776 L0.4663,0.5776 Q0.4704,0.5776,0.4785,0.5786 T0.4899,0.5796 Q0.4964,0.5796,0.5012,0.5767 Q0.5069,0.5737,0.5223,0.5737 Q0.5272,0.5737,0.5369,0.5742 T0.5499,0.5747 Q0.558,0.5747,0.5612,0.5737 Q0.5661,0.5717,0.5714,0.5712 T0.5835,0.5702 T0.5945,0.5692 T0.6018,0.5687 L0.6062,0.5687 T0.6095,0.5682 T0.6148,0.5678 Q0.6172,0.5668,0.6196,0.5653 T0.6245,0.5633 T0.631,0.5628 T0.6407,0.5638 T0.6504,0.5648 T0.6594,0.5638 T0.6675,0.5628 L0.6707,0.5628 L0.6861,0.5628 Q0.7364,0.5628,0.7494,0.5608 L0.7567,0.5608 L0.7652,0.5608 L0.7729,0.5608 Q0.7794,0.5608,0.7818,0.5589 Q0.7851,0.5569,0.7859,0.5559 Q0.7835,0.5539,0.7656,0.5519 Q0.7121,0.547,0.6975,0.543 Q0.6934,0.541,0.6853,0.5396 L0.6691,0.5366 T0.6525,0.5336 T0.6391,0.5312 Q0.6367,0.5302,0.6245,0.5257 T0.6034,0.5213 L0.5977,0.5213 Q0.5945,0.5223,0.5904,0.5223 Q0.5839,0.5223,0.5787,0.5208 T0.5685,0.5183 T0.5596,0.5173 Q0.558,0.5173,0.5552,0.5168 T0.5495,0.5163 L0.5434,0.5163 T0.5373,0.5158 T0.532,0.5148 T0.5284,0.5129 T0.5272,0.5104 L0.5272,0.5074 Q0.5304,0.5015,0.5377,0.499 T0.5629,0.4965 Q0.5734,0.4955,0.5811,0.4946 T0.5953,0.4916 T0.605,0.4896 Q0.6083,0.4886,0.6123,0.4886 Q0.6148,0.4886,0.6225,0.4891 T0.635,0.4896 L0.6456,0.4896 Q0.6837,0.4857,0.6861,0.4857 Q0.7226,0.4768,0.7283,0.4768 Q0.7307,0.4768,0.732,0.4773 T0.7372,0.4777 Q0.7502,0.4777,0.7721,0.4758 Q0.7753,0.4748,0.777,0.4738 Q0.7745,0.4718,0.768,0.4708 Q0.7599,0.4688,0.7445,0.4688 Q0.7405,0.4688,0.7324,0.4693 T0.721,0.4698 Q0.7056,0.4698,0.6999,0.4679 Q0.6861,0.4629,0.6626,0.4619 Q0.6586,0.4609,0.6533,0.4604 T0.6419,0.459 T0.6314,0.456 T0.6269,0.452 Q0.6269,0.451,0.6302,0.4491 Q0.6358,0.4451,0.6525,0.4416 T0.6788,0.4382 L0.6845,0.4382 L0.6902,0.4382 Q0.7064,0.4382,0.7145,0.4362 Q0.7202,0.4342,0.7307,0.4342 L0.7356,0.4342 L0.7409,0.4342 L0.7453,0.4342 Q0.7543,0.4342,0.7607,0.4332 Q0.7778,0.4313,0.7847,0.4288 T0.7932,0.4248 T0.7972,0.4169 T0.8045,0.4045 L0.8094,0.3986 T0.8135,0.3942 L0.8167,0.3912 T0.8191,0.3887 T0.8204,0.3867 T0.8208,0.3848 Q0.8208,0.3818,0.8167,0.3739 Q0.8127,0.3689,0.8054,0.3635 T0.7924,0.3556 T0.7778,0.3497 T0.768,0.3462 Q0.764,0.3442,0.7478,0.3427 T0.7267,0.3383 Q0.7234,0.3363,0.7044,0.3348 T0.6805,0.3323 L0.6764,0.3323 T0.6679,0.3328 T0.6586,0.3333 Q0.6521,0.3333,0.648,0.3314 Q0.6391,0.3264,0.6294,0.3264 Q0.6229,0.3264,0.6156,0.3294 Q0.6115,0.3304,0.6042,0.3304 Q0.5977,0.3304,0.5888,0.3289 T0.5738,0.3264 T0.5584,0.3225 L0.5491,0.3195 Q0.5418,0.3185,0.5162,0.3155 T0.4895,0.3121 T0.4826,0.3116 T0.4716,0.3111 T0.4607,0.3096 T0.4513,0.3066 T0.4477,0.3017 Q0.4477,0.2997,0.4485,0.2977 Q0.4526,0.2898,0.4611,0.2878 T0.4842,0.2859 Q0.4882,0.2859,0.4943,0.2864 T0.5028,0.2868 Q0.5118,0.2868,0.5215,0.2829 Q0.5401,0.276,0.5734,0.276 Q0.5831,0.276,0.5872,0.277 L0.5904,0.277 T0.5949,0.2745 T0.6034,0.272 Q0.631,0.269,0.6407,0.2661 Q0.6448,0.2641,0.6488,0.2641 T0.6573,0.2656 T0.6634,0.2671 Q0.6659,0.2671,0.6675,0.2651 Q0.6723,0.2601,0.6886,0.2552 T0.7137,0.2502 L0.7153,0.2502 L0.7178,0.2502 Q0.7218,0.2502,0.7279,0.2493 T0.7417,0.2468 T0.7526,0.2453 Q0.8208,0.2354,0.8281,0.2354 Q0.8337,0.2354,0.8394,0.2344 T0.8496,0.2315 T0.854,0.2265 Q0.854,0.2216,0.8443,0.2176 Q0.8354,0.2127,0.8216,0.2102 T0.7972,0.2072 T0.777,0.2067 T0.7648,0.2057 Q0.7632,0.2047,0.7498,0.2038 T0.7202,0.2018 T0.6983,0.1998 Q0.6918,0.1998,0.676,0.1949 T0.6537,0.1899 Q0.6496,0.1899,0.6472,0.1919 Q0.6342,0.1968,0.6123,0.1968 Q0.5961,0.1968,0.5912,0.1929 Q0.5904,0.1919,0.5892,0.1919 T0.5823,0.1944 T0.5722,0.1968 T0.5637,0.1949 Q0.5556,0.1909,0.5158,0.1869 Q0.5109,0.1869,0.5032,0.1835 T0.4915,0.18 Q0.4907,0.18,0.4891,0.181 Q0.4793,0.182,0.455,0.182 Q0.4177,0.182,0.4015,0.1771 Q0.3901,0.1741,0.3779,0.1741 Q0.3747,0.1741,0.369,0.1746 T0.3609,0.1751 Q0.3569,0.1751,0.3536,0.1741 Q0.3512,0.1741,0.3459,0.1736 T0.3358,0.1726 T0.3256,0.1716 T0.3167,0.1696 T0.3106,0.1662 Q0.3082,0.1632,0.3082,0.1592 Q0.3082,0.1543,0.3114,0.1508 T0.3204,0.1474 L0.3236,0.1474 Q0.3301,0.1494,0.3333,0.1494 Q0.339,0.1494,0.3496,0.1454 Q0.3674,0.1385,0.3869,0.1385 Q0.395,0.1385,0.4011,0.137 T0.4116,0.1355 T0.4197,0.1375 T0.425,0.1395 Q0.429,0.1395,0.4355,0.1365 Q0.4631,0.1266,0.4882,0.1266 Q0.5126,0.1266,0.5337,0.1246 Q0.5369,0.1246,0.5458,0.1207 T0.5661,0.1167 L0.5693,0.1167 Q0.5791,0.1177,0.5852,0.1187 T0.5921,0.1197 Q0.5945,0.1197,0.6002,0.1167 Q0.6026,0.1147,0.605,0.1123 T0.6091,0.1093 T0.6156,0.1088 T0.6318,0.1098 Q0.6456,0.1108,0.6569,0.1137 T0.6707,0.1167 Q0.674,0.1167,0.6772,0.1147 Q0.6821,0.1108,0.6946,0.1083 T0.721,0.1044 T0.7453,0.1029 L0.7526,0.1029 Q0.7583,0.1039,0.7652,0.1053 T0.7753,0.1068 Q0.7778,0.1068,0.7802,0.1058 Q0.7981,0.1019,0.8143,0.1019 Q0.8175,0.1019,0.8439,0.0964 T0.8735,0.091 Q0.8759,0.091,0.8836,0.089 T0.8938,0.0851 Q0.8929,0.0841,0.8905,0.0841 Q0.8832,0.0821,0.8735,0.0821 Q0.8694,0.0821,0.8629,0.0826 T0.854,0.0831 Q0.8508,0.0831,0.8491,0.0821 Q0.8475,0.0821,0.8171,0.0811 T0.7802,0.0781 T0.7571,0.0737 T0.7332,0.0712 Q0.7299,0.0712,0.7247,0.0717 T0.7161,0.0722 L0.7064,0.0722 Q0.7032,0.0712,0.7003,0.0707 T0.6959,0.0692 T0.693,0.0678 T0.6914,0.0663 T0.6902,0.0648 T0.689,0.0643 L0.6869,0.0643 L0.6845,0.0643 L0.6821,0.0643 Q0.6553,0.0643,0.6553,0.0554 T0.6796,0.0465 L0.6813,0.0465 L0.6845,0.0465 Q0.6918,0.0425,0.717,0.0376 Q0.7226,0.0366,0.7267,0.0356 T0.7324,0.0336 T0.7376,0.0317 T0.747,0.0297 Q0.7518,0.0287,0.7551,0.0287 T0.762,0.0297 T0.768,0.0307 L0.7729,0.0307 Q0.7802,0.0277,0.7997,0.0267 T0.8224,0.0237 Q0.8273,0.0227,0.8341,0.0223 T0.8475,0.0193 T0.8654,0.0143 T0.8873,0.0079 Q0.9002,0.003,0.9331,0.004 T0.97,0.004 Q0.9838,0,0.9976,0.001 Q1,0.001,1,0.003 Z M0.9935,0.996 L0.9951,0.0049 Q0.9822,0.0049,0.9716,0.0079 Q0.9659,0.0099,0.9327,0.0089 T0.8897,0.0119 Q0.8775,0.0158,0.8666,0.0183 T0.85,0.0237 Q0.8418,0.0267,0.8341,0.0272 T0.8248,0.0277 Q0.82,0.0297,0.8001,0.0312 T0.7745,0.0346 Q0.7697,0.0356,0.7664,0.0356 Q0.764,0.0356,0.7603,0.0341 T0.7534,0.0326 Q0.751,0.0326,0.7478,0.0336 Q0.7429,0.0346,0.7397,0.0356 T0.7344,0.0376 T0.7283,0.0401 T0.7178,0.0425 Q0.6926,0.0465,0.6878,0.0495 Q0.6845,0.0514,0.6764,0.0514 L0.6723,0.0514 L0.6691,0.0514 Q0.6602,0.0514,0.6602,0.0554 Q0.6602,0.0574,0.6659,0.0579 T0.6796,0.0589 T0.691,0.0603 T0.6967,0.0643 T0.7072,0.0673 Q0.7121,0.0682,0.7153,0.0682 T0.7238,0.0678 T0.7332,0.0673 Q0.7413,0.0673,0.7579,0.0692 T0.7818,0.0742 Q0.7867,0.0752,0.8171,0.0762 T0.8508,0.0781 L0.852,0.0781 T0.8601,0.0776 T0.8719,0.0772 Q0.8832,0.0772,0.8921,0.0791 Q0.8994,0.0811,0.8994,0.0841 T0.8933,0.0895 T0.8812,0.094 T0.8735,0.0959 T0.8451,0.1014 T0.8143,0.1068 Q0.7989,0.1068,0.7818,0.1098 Q0.7778,0.1108,0.7745,0.1108 T0.768,0.1103 T0.7603,0.1088 T0.7518,0.1068 L0.7461,0.1068 Q0.7315,0.1068,0.7088,0.1103 T0.6805,0.1177 Q0.6756,0.1207,0.6699,0.1207 Q0.6667,0.1207,0.6545,0.1177 T0.631,0.1147 Q0.6269,0.1137,0.6225,0.1133 T0.616,0.1128 L0.6123,0.1128 T0.6103,0.1133 L0.6083,0.1157 T0.6034,0.1197 Q0.5961,0.1246,0.5921,0.1246 Q0.5904,0.1246,0.5835,0.1231 T0.5693,0.1217 L0.5661,0.1217 Q0.558,0.1217,0.5535,0.1231 T0.5446,0.1266 T0.5345,0.1296 Q0.5126,0.1316,0.4882,0.1316 T0.438,0.1405 Q0.4298,0.1434,0.425,0.1434 Q0.4209,0.1434,0.4173,0.1419 T0.4112,0.1405 T0.4027,0.1419 T0.3869,0.1434 Q0.3682,0.1434,0.3512,0.1494 Q0.3398,0.1533,0.3333,0.1533 Q0.3285,0.1533,0.3228,0.1513 L0.3212,0.1513 Q0.3179,0.1513,0.3155,0.1538 T0.3131,0.1592 Q0.3131,0.1612,0.3147,0.1632 Q0.3171,0.1652,0.3232,0.1667 T0.3406,0.1691 T0.3544,0.1701 L0.3601,0.1701 L0.3678,0.1701 L0.3771,0.1701 Q0.3909,0.1701,0.4031,0.1731 Q0.4177,0.178,0.4558,0.178 Q0.4793,0.178,0.4882,0.1761 L0.4915,0.1761 Q0.498,0.1761,0.5061,0.179 T0.5158,0.183 Q0.5166,0.183,0.5231,0.1835 T0.5341,0.1845 T0.545,0.1855 T0.5572,0.1874 T0.5661,0.1909 Q0.5677,0.1919,0.5702,0.1919 T0.5791,0.1894 T0.588,0.1869 Q0.5921,0.1869,0.5945,0.1899 Q0.5977,0.1919,0.6123,0.1919 Q0.6334,0.1919,0.6448,0.1879 Q0.6488,0.186,0.6537,0.186 Q0.6626,0.186,0.6784,0.1909 T0.6983,0.1958 Q0.704,0.1968,0.7206,0.1973 T0.7506,0.1993 T0.7689,0.2018 L0.7705,0.2018 L0.7741,0.2018 L0.7786,0.2018 Q0.8248,0.2018,0.8475,0.2136 Q0.8597,0.2196,0.8597,0.2255 T0.8483,0.2359 T0.8281,0.2404 Q0.8208,0.2404,0.7534,0.2493 Q0.7502,0.2502,0.7425,0.2512 T0.7287,0.2532 T0.7178,0.2542 L0.7153,0.2542 L0.7137,0.2542 Q0.7064,0.2542,0.6906,0.2591 T0.6715,0.2681 T0.6626,0.272 Q0.6594,0.272,0.6541,0.2705 T0.6464,0.269 T0.6431,0.27 Q0.6318,0.273,0.6034,0.276 Q0.6018,0.276,0.5998,0.2774 T0.5957,0.2799 T0.5904,0.2809 L0.5856,0.2809 Q0.5823,0.2799,0.575,0.2799 Q0.541,0.2799,0.5239,0.2868 Q0.5134,0.2908,0.5028,0.2908 Q0.4996,0.2908,0.4935,0.2903 T0.4842,0.2898 Q0.4704,0.2898,0.4635,0.2918 T0.4534,0.2997 L0.4534,0.3007 Q0.4534,0.3037,0.459,0.3046 T0.4753,0.3066 T0.4899,0.3076 Q0.4907,0.3076,0.5166,0.3106 T0.5507,0.3155 Q0.5921,0.3264,0.605,0.3264 Q0.6107,0.3264,0.6131,0.3254 Q0.6212,0.3225,0.6294,0.3225 Q0.6407,0.3225,0.6513,0.3274 Q0.6529,0.3294,0.6569,0.3294 Q0.6594,0.3294,0.665,0.3284 T0.6748,0.3274 T0.6821,0.3284 Q0.6861,0.3294,0.6967,0.3299 T0.7165,0.3314 T0.7299,0.3343 Q0.7324,0.3363,0.7486,0.3383 T0.7697,0.3422 Q0.7713,0.3432,0.7774,0.3452 T0.7887,0.3492 T0.8005,0.3546 T0.8127,0.3625 T0.8208,0.3719 Q0.8256,0.3798,0.8256,0.3848 Q0.8256,0.3877,0.8236,0.3902 T0.8171,0.3976 T0.809,0.4075 T0.8021,0.4189 T0.7968,0.4273 T0.7867,0.4322 T0.7616,0.4382 Q0.7543,0.4392,0.7437,0.4392 L0.7364,0.4392 L0.7299,0.4392 Q0.7202,0.4392,0.7161,0.4402 Q0.7072,0.4431,0.691,0.4431 L0.6845,0.4431 L0.6788,0.4431 Q0.6707,0.4431,0.6545,0.4461 T0.6334,0.452 Q0.6399,0.456,0.6626,0.457 Q0.6878,0.458,0.7024,0.4639 Q0.7064,0.4649,0.7186,0.4649 L0.7295,0.4649 L0.7413,0.4649 Q0.7591,0.4649,0.7697,0.4669 Q0.7818,0.4688,0.7818,0.4728 Q0.7818,0.4748,0.7798,0.4763 T0.7753,0.4787 L0.7737,0.4797 L0.7729,0.4797 Q0.7494,0.4827,0.7388,0.4827 Q0.7332,0.4827,0.7307,0.4817 L0.7291,0.4817 Q0.7226,0.4817,0.6878,0.4896 Q0.6829,0.4906,0.6772,0.4911 T0.6618,0.4921 T0.646,0.4931 T0.6342,0.4936 T0.6212,0.4931 T0.6115,0.4926 Q0.6083,0.4926,0.6058,0.4931 T0.5969,0.4955 T0.5819,0.499 T0.5629,0.5005 Q0.5572,0.5005,0.5527,0.501 T0.545,0.502 T0.5397,0.503 T0.5361,0.5045 T0.5337,0.5064 T0.5328,0.5094 L0.532,0.5094 Q0.532,0.5104,0.5337,0.5109 T0.5381,0.5114 T0.5438,0.5119 T0.5515,0.5124 L0.5604,0.5124 Q0.5661,0.5134,0.5746,0.5153 T0.5912,0.5173 L0.5977,0.5173 L0.6026,0.5173 Q0.6139,0.5173,0.6273,0.5218 T0.6407,0.5272 Q0.6448,0.5282,0.6683,0.5321 T0.6991,0.5381 Q0.7137,0.543,0.7664,0.548 Q0.7908,0.55,0.7908,0.5559 Q0.7908,0.5589,0.7859,0.5628 Q0.781,0.5658,0.7713,0.5658 Q0.7689,0.5658,0.7628,0.5653 T0.7543,0.5648 T0.7502,0.5658 Q0.7364,0.5678,0.6878,0.5678 Q0.674,0.5678,0.6699,0.5668 L0.6683,0.5668 Q0.6659,0.5668,0.6606,0.5678 T0.6513,0.5687 Q0.6464,0.5687,0.6391,0.5678 Q0.6334,0.5668,0.6302,0.5668 T0.6241,0.5687 T0.6164,0.5717 Q0.6123,0.5727,0.6099,0.5727 L0.6062,0.5727 T0.6022,0.5732 T0.5953,0.5737 Q0.5904,0.5747,0.5839,0.5747 T0.5726,0.5752 T0.5629,0.5776 T0.5458,0.5796 Q0.5418,0.5796,0.5324,0.5791 T0.5191,0.5786 Q0.5077,0.5786,0.5045,0.5806 Q0.498,0.5836,0.4899,0.5836 Q0.4858,0.5836,0.4777,0.5826 T0.4655,0.5816 L0.4631,0.5816 Q0.4582,0.5816,0.4436,0.5841 T0.4144,0.5865 L0.4051,0.5865 L0.3986,0.5865 T0.3933,0.587 T0.3897,0.5875 T0.3869,0.5885 T0.3844,0.5895 T0.3812,0.591 T0.3779,0.5925 Q0.3739,0.5955,0.3633,0.5955 L0.3585,0.5955 L0.3536,0.5955 Q0.3277,0.5955,0.3066,0.6053 Q0.3017,0.6083,0.296,0.6083 Q0.2912,0.6083,0.2839,0.6068 T0.2741,0.6053 Q0.2693,0.6053,0.2547,0.6048 T0.236,0.6044 Q0.2222,0.6044,0.193,0.6103 L0.1882,0.6103 Q0.1825,0.6103,0.1703,0.6083 T0.1525,0.6063 L0.15,0.6063 Q0.1436,0.6063,0.135,0.6073 T0.1168,0.6098 T0.1038,0.6113 Q0.0973,0.6123,0.0787,0.6123 L0.06,0.6123 L0.0414,0.6123 L0.0235,0.6123 Q0.0049,0.6133,0.0049,0.6172 L0.0057,0.6222 Q0.0073,0.6261,0.043,0.6261 L0.0454,0.6261 L0.0479,0.6261 Q0.0527,0.6261,0.0633,0.6281 T0.0803,0.6301 L0.0835,0.6301 L0.0916,0.6301 Q0.0998,0.6301,0.129,0.632 Q0.1768,0.636,0.206,0.64 Q0.2117,0.6409,0.2165,0.6409 Q0.2206,0.6409,0.2263,0.6405 T0.2336,0.64 L0.2384,0.64 L0.2409,0.64 T0.251,0.6395 T0.2644,0.639 Q0.2741,0.639,0.279,0.6409 Q0.283,0.6429,0.2972,0.6439 T0.3293,0.6459 T0.3528,0.6469 L0.3577,0.6469 Q0.3642,0.6469,0.3723,0.6449 T0.3844,0.6429 Q0.3901,0.6429,0.3933,0.6454 T0.4006,0.6479 Q0.4088,0.6479,0.4517,0.6558 Q0.4582,0.6568,0.4623,0.6568 Q0.4655,0.6568,0.4716,0.6563 T0.4826,0.6558 L0.4882,0.6558 Q0.5004,0.6558,0.5255,0.6607 Q0.5328,0.6627,0.5604,0.6647 T0.6152,0.6682 T0.644,0.6696 T0.6719,0.6721 T0.7056,0.6756 Q0.7113,0.6756,0.7198,0.6805 T0.738,0.6855 Q0.7453,0.6855,0.7551,0.6879 T0.7741,0.6939 T0.7912,0.7018 T0.8029,0.7102 Q0.8054,0.7132,0.8054,0.7151 Q0.8054,0.7191,0.8009,0.7216 T0.7908,0.7255 T0.779,0.7285 T0.7721,0.731 Q0.7672,0.7329,0.7632,0.7339 T0.7551,0.7354 T0.7486,0.7369 Q0.7478,0.7369,0.7445,0.7379 T0.7388,0.7394 T0.7324,0.7404 L0.7242,0.7413 T0.7161,0.7418 Q0.7105,0.7418,0.6955,0.7428 T0.6748,0.7438 L0.6699,0.7438 L0.6667,0.7438 Q0.6659,0.7438,0.6407,0.7468 Q0.6367,0.7478,0.6334,0.7478 T0.6245,0.7468 T0.6148,0.7458 L0.6099,0.7458 Q0.6018,0.7468,0.5864,0.7498 T0.5653,0.7532 T0.5596,0.7557 Q0.5596,0.7596,0.571,0.7616 Q0.5742,0.7626,0.5803,0.7631 T0.5929,0.7641 T0.6018,0.7656 L0.6087,0.7656 T0.6192,0.7666 T0.6277,0.7695 Q0.631,0.7705,0.6342,0.7705 Q0.6383,0.7705,0.6452,0.7685 T0.6537,0.7666 L0.6553,0.7666 Q0.6602,0.7666,0.6711,0.769 T0.6853,0.7715 Q0.691,0.7715,0.7048,0.775 T0.7242,0.7814 Q0.7324,0.7854,0.7445,0.7864 Q0.7478,0.7864,0.768,0.7873 T0.7972,0.7893 Q0.7989,0.7893,0.8017,0.7898 T0.8054,0.7903 L0.8082,0.7903 T0.811,0.7908 T0.8131,0.7913 T0.8151,0.7918 L0.8167,0.7928 T0.8179,0.7943 T0.8191,0.7957 T0.8204,0.7977 T0.8208,0.8002 Q0.8208,0.8032,0.8171,0.8051 T0.8074,0.8081 T0.7997,0.8091 Q0.7932,0.8121,0.7737,0.8121 Q0.7664,0.8121,0.7413,0.8111 L0.7364,0.8111 Q0.7275,0.8111,0.7214,0.8121 T0.7084,0.8155 T0.6991,0.819 Q0.6934,0.82,0.6723,0.8205 T0.6472,0.8239 Q0.6423,0.8269,0.6375,0.8269 Q0.6358,0.8269,0.6338,0.8264 T0.6294,0.8254 T0.6253,0.8249 Q0.6042,0.8249,0.5961,0.8289 Q0.5921,0.8318,0.5839,0.8318 Q0.5783,0.8318,0.5645,0.8299 L0.5604,0.8299 Q0.5531,0.8299,0.5337,0.8368 Q0.5296,0.8378,0.5255,0.8378 Q0.5239,0.8378,0.5195,0.8373 T0.5118,0.8368 Q0.5061,0.8368,0.4996,0.8388 Q0.4947,0.8388,0.4882,0.8398 Q0.4777,0.8417,0.4753,0.8427 T0.4728,0.8467 Q0.4728,0.8487,0.4769,0.8492 T0.4886,0.8501 T0.5004,0.8516 Q0.5036,0.8516,0.515,0.8521 T0.5345,0.8546 Q0.5426,0.8556,0.5596,0.8556 L0.5649,0.8556 L0.5702,0.8556 L0.5791,0.8556 Q0.5921,0.8566,0.6135,0.8571 T0.6383,0.8586 Q0.6683,0.8615,0.6764,0.8615 L0.6776,0.8615 L0.6788,0.8615 Q0.7032,0.8615,0.7032,0.8684 Q0.7032,0.8714,0.6995,0.8749 T0.6959,0.8803 L0.6959,0.8813 Q0.6991,0.8843,0.7153,0.8843 Q0.7218,0.8843,0.7259,0.8833 L0.7299,0.8833 Q0.7324,0.8833,0.7348,0.8838 T0.7393,0.8848 T0.7433,0.8863 L0.7474,0.8882 T0.751,0.8892 Q0.7616,0.8922,0.7948,0.8952 Q0.7981,0.8952,0.8082,0.8981 T0.8297,0.9011 T0.8528,0.9026 T0.8747,0.907 T0.8856,0.9149 L0.8856,0.9159 Q0.8856,0.9179,0.8848,0.9189 T0.8828,0.9209 T0.88,0.9228 T0.8767,0.9243 T0.8731,0.9248 T0.8698,0.9253 T0.8666,0.9258 L0.8646,0.9258 Q0.8621,0.9258,0.8577,0.9263 T0.85,0.9268 T0.8439,0.9273 T0.8402,0.9278 Q0.8386,0.9298,0.8358,0.9308 T0.8305,0.9322 T0.8244,0.9332 T0.8183,0.9337 Q0.8135,0.9347,0.8025,0.9372 T0.7859,0.9416 Q0.7778,0.9436,0.7717,0.9471 T0.7624,0.9515 Q0.7575,0.9535,0.7486,0.9535 Q0.7461,0.9535,0.7397,0.953 T0.7291,0.9525 Q0.7234,0.9525,0.7186,0.953 T0.7137,0.957 T0.7194,0.9614 Q0.7567,0.9634,0.7664,0.9654 Q0.7745,0.9674,0.7762,0.9674 L0.7908,0.9674 Q0.7924,0.9664,0.7948,0.9664 Q0.8029,0.9664,0.8082,0.9679 T0.8224,0.9723 T0.8362,0.9773 Q0.8483,0.9812,0.871,0.9812 L0.8751,0.9812 Q0.8873,0.9812,0.8929,0.9822 T0.9165,0.9876 T0.9408,0.9931 Q0.9513,0.996,0.9935,0.996 Z M0.9968,0.998 L0.9976,0.003 Q0.9838,0.002,0.9708,0.0059 Q0.9659,0.0069,0.9327,0.0059 T0.8881,0.0099 Q0.8767,0.0138,0.8658,0.0163 T0.8491,0.0218 Q0.8418,0.0247,0.8341,0.0247 T0.8232,0.0257 Q0.8191,0.0277,0.7997,0.0292 T0.7737,0.0326 Q0.7697,0.0336,0.7672,0.0336 T0.7612,0.0321 T0.7543,0.0307 T0.747,0.0317 Q0.7397,0.0326,0.7336,0.0356 T0.717,0.0406 Q0.6918,0.0445,0.6861,0.0475 Q0.6845,0.0485,0.6772,0.0485 T0.6638,0.05 T0.6577,0.0549 T0.6642,0.0598 T0.6796,0.0613 T0.6902,0.0623 Q0.691,0.0623,0.6946,0.0658 T0.7064,0.0702 L0.7161,0.0702 Q0.7194,0.0702,0.7242,0.0697 T0.7332,0.0692 Q0.7413,0.0692,0.7579,0.0717 T0.781,0.0762 Q0.7867,0.0781,0.8171,0.0786 T0.85,0.0801 L0.8532,0.0801 Q0.8548,0.0801,0.8613,0.0796 T0.8727,0.0791 Q0.8832,0.0791,0.8913,0.0811 Q0.897,0.0831,0.897,0.0851 Q0.897,0.088,0.8873,0.0905 T0.8735,0.093 Q0.871,0.093,0.8447,0.0984 T0.8143,0.1039 Q0.7981,0.1039,0.781,0.1078 Q0.7778,0.1088,0.7745,0.1088 T0.7644,0.1073 T0.7518,0.1048 L0.7453,0.1048 Q0.7299,0.1048,0.7076,0.1083 T0.6788,0.1157 Q0.6748,0.1187,0.6699,0.1187 Q0.6675,0.1187,0.6557,0.1157 T0.6318,0.1118 Q0.6164,0.1108,0.6135,0.1108 T0.6095,0.1113 T0.6067,0.1142 T0.6018,0.1187 Q0.5953,0.1227,0.5921,0.1227 Q0.5904,0.1227,0.5839,0.1212 T0.5693,0.1187 L0.5661,0.1187 Q0.558,0.1187,0.5527,0.1207 T0.5434,0.1246 T0.5337,0.1266 Q0.5126,0.1296,0.4882,0.1296 T0.4371,0.1385 Q0.4298,0.1414,0.425,0.1414 Q0.4225,0.1414,0.4189,0.1395 T0.4112,0.1375 Q0.4079,0.1375,0.4019,0.1395 T0.3869,0.1414 Q0.3682,0.1414,0.3504,0.1474 Q0.3398,0.1513,0.3333,0.1513 Q0.3293,0.1513,0.3228,0.1494 L0.3204,0.1494 Q0.3179,0.1494,0.3155,0.1508 T0.3118,0.1543 T0.3106,0.1592 T0.3127,0.1642 T0.32,0.1677 T0.3305,0.1701 T0.3435,0.1716 T0.3544,0.1721 Q0.3569,0.1731,0.3601,0.1731 Q0.3625,0.1731,0.3686,0.1726 T0.3779,0.1721 Q0.3901,0.1721,0.4023,0.1751 Q0.4177,0.18,0.455,0.18 Q0.4793,0.18,0.4882,0.178 L0.4915,0.178 Q0.4964,0.178,0.5045,0.1815 T0.5158,0.185 Q0.5564,0.1889,0.5645,0.1929 Q0.5677,0.1939,0.571,0.1939 Q0.575,0.1939,0.5807,0.1919 T0.5888,0.1899 T0.5929,0.1909 Q0.5969,0.1949,0.6123,0.1949 Q0.6334,0.1949,0.6456,0.1899 Q0.6488,0.1879,0.6537,0.1879 Q0.661,0.1879,0.6772,0.1929 T0.6983,0.1978 Q0.704,0.1988,0.7206,0.1998 T0.7502,0.2018 T0.7664,0.2038 Q0.7672,0.2038,0.7818,0.2043 T0.8151,0.2067 T0.8459,0.2156 Q0.8573,0.2206,0.8573,0.2255 Q0.8573,0.2295,0.8516,0.2324 T0.8394,0.2369 T0.8281,0.2384 Q0.8208,0.2384,0.7526,0.2473 Q0.7494,0.2483,0.7421,0.2493 T0.7287,0.2512 T0.7178,0.2522 L0.7153,0.2522 L0.7137,0.2522 Q0.7056,0.2522,0.6898,0.2572 T0.6691,0.2671 Q0.6667,0.269,0.6634,0.269 Q0.661,0.269,0.6557,0.2681 T0.6472,0.2671 Q0.6448,0.2671,0.6415,0.2681 Q0.631,0.271,0.6034,0.274 Q0.5994,0.274,0.5961,0.2765 T0.5904,0.2789 L0.5864,0.2789 Q0.5831,0.2779,0.5742,0.2779 Q0.541,0.2779,0.5223,0.2849 Q0.5126,0.2888,0.5028,0.2888 Q0.5004,0.2888,0.4943,0.2883 T0.4842,0.2878 Q0.4704,0.2878,0.4627,0.2898 T0.4509,0.2987 Q0.4501,0.2997,0.4501,0.3007 Q0.4501,0.3046,0.4574,0.3066 T0.4765,0.3091 T0.4899,0.3096 Q0.4931,0.3106,0.5061,0.3121 T0.5316,0.315 T0.5499,0.3175 Q0.5507,0.3175,0.5572,0.3195 T0.5685,0.3225 T0.5803,0.3254 T0.5933,0.3279 T0.605,0.3284 T0.6148,0.3274 Q0.6221,0.3244,0.6294,0.3244 Q0.6399,0.3244,0.6496,0.3294 Q0.6529,0.3314,0.6577,0.3314 Q0.6602,0.3314,0.6663,0.3304 T0.6756,0.3294 T0.6813,0.3304 Q0.6837,0.3304,0.6873,0.3309 L0.6946,0.3318 T0.7024,0.3323 T0.7105,0.3328 T0.7178,0.3338 T0.7238,0.3348 T0.7283,0.3363 Q0.7324,0.3383,0.7482,0.3403 T0.7689,0.3442 Q0.7697,0.3442,0.779,0.3477 T0.794,0.3536 T0.8074,0.3615 T0.8183,0.3729 Q0.8232,0.3808,0.8232,0.3848 Q0.8232,0.3867,0.8216,0.3892 T0.8151,0.3961 T0.8062,0.406 T0.7997,0.4179 T0.7952,0.4263 T0.7855,0.4308 T0.7616,0.4362 Q0.7543,0.4372,0.7445,0.4372 L0.7372,0.4372 L0.7307,0.4372 Q0.7202,0.4372,0.7153,0.4382 Q0.7064,0.4402,0.691,0.4402 L0.6845,0.4402 L0.6788,0.4402 Q0.6699,0.4402,0.6533,0.4436 T0.6318,0.451 Q0.6302,0.451,0.6302,0.452 Q0.6302,0.457,0.6626,0.459 Q0.6869,0.4609,0.7015,0.4659 Q0.7064,0.4669,0.7202,0.4669 L0.7311,0.4669 L0.7429,0.4669 Q0.7599,0.4669,0.7689,0.4688 Q0.7794,0.4708,0.7794,0.4738 L0.7786,0.4748 T0.777,0.4763 T0.7749,0.4773 L0.7737,0.4777 L0.7729,0.4777 Q0.7502,0.4797,0.738,0.4797 L0.7315,0.4797 L0.7291,0.4797 Q0.7226,0.4797,0.6869,0.4876 Q0.6821,0.4886,0.6768,0.4891 T0.6614,0.4901 T0.6456,0.4916 L0.6342,0.4916 Q0.6294,0.4916,0.6217,0.4911 T0.6115,0.4906 Q0.6083,0.4906,0.6054,0.4911 T0.5961,0.4936 T0.5815,0.4965 T0.5629,0.4985 Q0.5507,0.4985,0.5442,0.4995 T0.5345,0.5025 T0.5304,0.5084 L0.5296,0.5094 Q0.5296,0.5114,0.5316,0.5124 T0.5373,0.5138 T0.5442,0.5143 L0.5523,0.5143 L0.5604,0.5143 Q0.5637,0.5153,0.5677,0.5158 T0.5742,0.5173 T0.5811,0.5188 T0.5904,0.5193 L0.5977,0.5193 L0.6026,0.5193 Q0.6107,0.5193,0.6168,0.5208 T0.6294,0.5252 T0.6399,0.5292 Q0.6448,0.5302,0.6679,0.5341 T0.6983,0.541 Q0.7129,0.545,0.7664,0.55 Q0.7883,0.5519,0.7883,0.5559 Q0.7883,0.5579,0.7843,0.5608 T0.7721,0.5638 Q0.7697,0.5638,0.764,0.5633 T0.7559,0.5628 L0.7502,0.5628 Q0.7364,0.5648,0.6869,0.5648 L0.6699,0.5648 L0.6675,0.5648 Q0.665,0.5648,0.6602,0.5658 T0.6513,0.5668 Q0.6464,0.5668,0.6399,0.5658 Q0.6342,0.5648,0.6302,0.5648 T0.6225,0.5668 T0.6156,0.5697 Q0.6107,0.5707,0.6087,0.5707 T0.6034,0.5712 T0.5953,0.5717 Q0.5904,0.5727,0.5835,0.5727 T0.5718,0.5732 T0.562,0.5757 Q0.558,0.5767,0.5483,0.5767 L0.5349,0.5767 L0.5207,0.5767 Q0.5069,0.5767,0.5028,0.5786 Q0.4972,0.5816,0.4899,0.5816 Q0.4858,0.5816,0.4781,0.5806 T0.4663,0.5796 L0.4631,0.5796 Q0.4582,0.5796,0.4432,0.5821 T0.4144,0.5846 L0.4071,0.5846 L0.4011,0.5846 T0.3958,0.5851 T0.3921,0.5856 T0.3889,0.5861 T0.3865,0.5865 T0.384,0.5875 L0.382,0.5885 L0.3796,0.5895 L0.3771,0.5905 Q0.3731,0.5925,0.365,0.5925 L0.3601,0.5925 L0.3552,0.5925 Q0.3277,0.5925,0.3049,0.6044 Q0.3017,0.6053,0.296,0.6053 Q0.292,0.6053,0.2847,0.6044 T0.2741,0.6034 Q0.2693,0.6034,0.2547,0.6029 T0.236,0.6024 Q0.2222,0.6024,0.1922,0.6073 Q0.1906,0.6083,0.1882,0.6083 Q0.1833,0.6083,0.1711,0.6063 T0.1533,0.6044 Q0.1517,0.6034,0.1492,0.6034 Q0.1436,0.6034,0.135,0.6048 T0.1168,0.6078 T0.103,0.6093 Q0.0973,0.6103,0.0787,0.6103 L0.0604,0.6103 L0.0414,0.6103 L0.0235,0.6103 Q0.0024,0.6113,0.0024,0.6172 Q0.0024,0.6192,0.0032,0.6231 Q0.0049,0.6281,0.0333,0.6281 L0.0414,0.6281 L0.0479,0.6281 Q0.0519,0.6281,0.0624,0.6301 T0.0803,0.632 L0.0835,0.632 L0.0916,0.632 Q0.0989,0.632,0.1281,0.634 L0.206,0.6419 Q0.2117,0.6429,0.2174,0.6429 Q0.2214,0.6429,0.2271,0.6424 T0.2344,0.6419 L0.2376,0.6419 Q0.2393,0.6429,0.2417,0.6429 T0.2526,0.6419 T0.2652,0.6409 Q0.2741,0.6409,0.2782,0.6429 T0.2968,0.6459 T0.3293,0.6479 T0.352,0.6489 Q0.3552,0.6499,0.3585,0.6499 Q0.365,0.6499,0.3731,0.6474 T0.3844,0.6449 Q0.3885,0.6449,0.3917,0.6474 T0.4006,0.6499 Q0.4088,0.6499,0.4509,0.6578 Q0.4574,0.6588,0.4631,0.6588 Q0.4655,0.6588,0.4716,0.6583 T0.4834,0.6578 L0.4874,0.6578 Q0.5004,0.6588,0.5255,0.6637 Q0.532,0.6647,0.56,0.6667 T0.6148,0.6701 T0.644,0.6716 Q0.6464,0.6726,0.6594,0.6736 T0.6861,0.6756 T0.7048,0.6775 Q0.7097,0.6775,0.7186,0.6825 T0.738,0.6874 Q0.7518,0.6874,0.7729,0.6958 T0.8005,0.7122 Q0.8029,0.7141,0.8029,0.7151 Q0.8029,0.7191,0.7968,0.7216 T0.7818,0.726 T0.7705,0.729 L0.764,0.731 T0.7583,0.7324 T0.753,0.7334 T0.7486,0.7349 Q0.7251,0.7399,0.7161,0.7399 Q0.7097,0.7399,0.6951,0.7409 T0.6748,0.7418 L0.6703,0.7418 L0.6667,0.7418 Q0.6659,0.7418,0.6399,0.7448 L0.6334,0.7448 Q0.631,0.7448,0.6249,0.7443 T0.6148,0.7438 L0.6099,0.7438 Q0.601,0.7448,0.5856,0.7473 T0.5645,0.7507 Q0.5572,0.7517,0.5572,0.7547 T0.5616,0.7606 T0.5702,0.7646 Q0.5734,0.7646,0.5779,0.7651 T0.5864,0.7661 T0.5949,0.7671 T0.6018,0.7676 L0.6071,0.7676 T0.6135,0.7681 T0.62,0.769 T0.6269,0.771 T0.6342,0.7725 Q0.6391,0.7725,0.6464,0.7705 T0.6541,0.7685 L0.6553,0.7685 Q0.6586,0.7685,0.6699,0.771 T0.6853,0.7735 Q0.6902,0.7735,0.704,0.777 T0.7226,0.7834 Q0.7315,0.7873,0.7445,0.7883 Q0.747,0.7883,0.7676,0.7893 T0.7972,0.7913 Q0.7989,0.7913,0.8017,0.7918 T0.8058,0.7923 T0.809,0.7928 T0.8114,0.7933 T0.8131,0.7938 T0.8147,0.7943 T0.8159,0.7953 L0.8171,0.7967 T0.8175,0.7982 Q0.8183,0.7992,0.8183,0.8002 Q0.8183,0.8022,0.8155,0.8032 T0.807,0.8051 T0.7989,0.8071 Q0.7932,0.8091,0.7737,0.8091 L0.7413,0.8091 L0.7372,0.8091 Q0.7275,0.8091,0.721,0.8101 T0.7072,0.8136 T0.6983,0.817 Q0.6942,0.818,0.6825,0.818 T0.6602,0.819 T0.6456,0.822 Q0.6415,0.8249,0.6375,0.8249 Q0.6367,0.8249,0.6326,0.8239 T0.6253,0.8229 Q0.6034,0.8229,0.5953,0.8279 Q0.5912,0.8289,0.5848,0.8289 Q0.5775,0.8289,0.5645,0.8279 Q0.5629,0.8269,0.5604,0.8269 Q0.5523,0.8269,0.5328,0.8348 Q0.5296,0.8358,0.5264,0.8358 Q0.5247,0.8358,0.5203,0.8353 T0.5126,0.8348 Q0.5061,0.8348,0.4988,0.8358 Q0.4947,0.8368,0.4882,0.8378 Q0.4826,0.8388,0.4797,0.8388 T0.474,0.8398 T0.4704,0.8422 T0.4696,0.8462 T0.472,0.8501 T0.4781,0.8521 T0.4854,0.8526 L0.4935,0.8526 T0.4996,0.8536 Q0.5036,0.8536,0.515,0.8546 T0.5337,0.8566 Q0.5426,0.8576,0.5596,0.8576 L0.5657,0.8576 L0.571,0.8576 L0.5791,0.8576 Q0.5921,0.8586,0.6135,0.8591 T0.6383,0.8605 Q0.6683,0.8635,0.6764,0.8635 Q0.6999,0.8635,0.6999,0.8684 Q0.6999,0.8694,0.6967,0.8734 T0.6934,0.8803 L0.6934,0.8823 Q0.6951,0.8863,0.7137,0.8863 Q0.7218,0.8863,0.7267,0.8853 L0.7299,0.8853 Q0.734,0.8853,0.7409,0.8882 T0.7502,0.8912 Q0.7607,0.8942,0.794,0.8971 Q0.7972,0.8971,0.8078,0.9001 T0.8297,0.9031 Q0.8475,0.9031,0.8646,0.9065 T0.8824,0.9149 L0.8824,0.9159 Q0.8824,0.9169,0.882,0.9179 T0.8808,0.9194 L0.8792,0.9204 T0.8767,0.9214 T0.8743,0.9224 T0.8715,0.9228 L0.8686,0.9228 T0.8662,0.9233 T0.8637,0.9238 L0.8556,0.9238 T0.8447,0.9243 T0.8386,0.9268 Q0.837,0.9278,0.835,0.9288 T0.8305,0.9303 T0.8244,0.9308 T0.8175,0.9318 Q0.8127,0.9327,0.8017,0.9352 T0.7851,0.9397 Q0.777,0.9416,0.7705,0.9451 T0.7616,0.9496 Q0.7575,0.9505,0.7494,0.9505 L0.7405,0.9505 L0.7307,0.9505 Q0.7234,0.9505,0.7178,0.9515 Q0.7145,0.9515,0.7125,0.9535 T0.7105,0.9575 Q0.7105,0.9624,0.7194,0.9634 Q0.7567,0.9654,0.7664,0.9674 Q0.7745,0.9693,0.7762,0.9693 L0.7908,0.9693 L0.7948,0.9693 Q0.8005,0.9693,0.8058,0.9698 T0.8143,0.9718 T0.824,0.9753 T0.8354,0.9792 Q0.8483,0.9832,0.871,0.9832 L0.8739,0.9832 L0.8767,0.9832 Q0.8873,0.9832,0.8921,0.9842 Q0.8978,0.9852,0.9157,0.9896 T0.94,0.996 Q0.9424,0.996,0.9469,0.9965 T0.9566,0.997 T0.9676,0.9975 T0.9781,0.998 L0.9874,0.998 L0.9943,0.998 L0.9968,0.998 Z', type: CustomGlyphVectorType.FILL } },\n  // '\\u{E0CB}' is unused\n  '\\u{E0CC}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0.5071 L0.8623,0.7102 L0.5865,0.7102 L0.4489,0.5071 L0.5865,0.3045 L0.8623,0.3045 Z M0.4135,0.7974 L0.2758,1 L0,1 L0,0.5944 L0.2758,0.5944 Z M0.4135,0.2026 L0.2758,0.4056 L0,0.4056 L0,0 L0.2758,0 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0CD}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.9726,0.5071 L0.8451,0.3184 L0.59,0.3184 L0.4624,0.5071 L0.59,0.6955 L0.8451,0.6955 Z M1,0.5071 L0.8588,0.716 L0.5768,0.716 L0.4355,0.5071 L0.5768,0.2982 L0.8588,0.2982 Z M0.401,0.7911 L0.2735,0.6028 L0.0236,0.6028 L0.0236,0.9799 L0.2735,0.9799 Z M0.4284,0.7911 L0.2872,1 L0,1 L0,0.5826 L0.2872,0.5826 Z M0.401,0.2089 L0.2735,0.0201 L0.0236,0.0201 L0.0236,0.3972 L0.2735,0.3972 Z M0.4284,0.2089 L0.2872,0.4174 L0,0.4174 L0,0 L0.2872,0 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0CE}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0.4996 Q1,0.5365,0.9924,0.568 T0.9718,0.6177 T0.9433,0.6363 L0.9425,0.6367 Q0.9355,0.6367,0.9285,0.6326 L0.9308,0.6326 Q0.9129,0.6195,0.9013,0.587 T0.8871,0.5164 T0.8943,0.4373 T0.9254,0.3687 L0.9254,0.3691 Q0.9339,0.3628,0.9425,0.3628 Q0.9584,0.3628,0.9716,0.3811 T0.9924,0.431 T1,0.4996 Z M0.9234,0.6321 L0.8776,0.7169 Q0.8791,0.732,0.8791,0.7441 Q0.8791,0.781,0.8716,0.8123 T0.8508,0.862 T0.8224,0.8809 L0.8216,0.8809 Q0.8154,0.8809,0.8092,0.8775 L0.8088,0.8775 Q0.7905,0.8633,0.7792,0.8297 T0.766,0.7571 T0.775,0.6768 T0.8088,0.6086 L0.7431,0.6065 Q0.719,0.6313,0.7072,0.6705 T0.6965,0.7487 T0.7097,0.823 T0.7423,0.8763 L0.7913,0.8775 L0.7314,0.9895 L0.4648,0.5038 L0.7299,0.0176 L0.7882,0.1229 L0.7404,0.1212 Q0.7244,0.138,0.7136,0.1617 T0.6982,0.2108 T0.6943,0.2632 T0.7003,0.3144 T0.7159,0.3586 T0.7396,0.3909 L0.8072,0.3926 Q0.7894,0.3796,0.7777,0.3479 T0.7635,0.2789 T0.7697,0.2011 T0.7987,0.1326 Q0.8092,0.1225,0.8204,0.1225 Q0.8321,0.1225,0.8426,0.1332 T0.8609,0.1623 T0.8731,0.2062 T0.8776,0.2592 Q0.8776,0.268,0.8768,0.2815 L0.9219,0.3628 L0.8644,0.3612 Q0.8403,0.3855,0.8284,0.4247 T0.8177,0.5029 T0.8309,0.5772 T0.8632,0.6305 Z M0.6759,0.6384 Q0.6576,0.625,0.6457,0.5914 T0.6319,0.5185 T0.6407,0.4375 T0.6751,0.3691 L0.609,0.367 Q0.5849,0.3918,0.5731,0.431 T0.5626,0.509 T0.5758,0.5833 T0.6082,0.6367 Z M0.6459,0.0025 L0.391,0.4551 L0.0035,0.4551 L0.246,0 Z M0.6553,1 L0.2421,1 L0,0.5449 L0.3875,0.5449 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0CF}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.4996,0 Q0.5562,0,0.5961,0.0166 T0.6364,0.0566 L0.6368,0.0575 Q0.6368,0.0646,0.6324,0.0713 L0.6324,0.0692 Q0.6196,0.0868,0.5872,0.0986 T0.5166,0.1128 T0.4373,0.1057 T0.3689,0.0747 L0.3694,0.0747 Q0.3627,0.0663,0.3627,0.0575 Q0.3627,0.0336,0.4028,0.0168 T0.4996,0 Z M0.6324,0.0768 L0.717,0.1225 Q0.7321,0.1212,0.744,0.1212 Q0.8007,0.1212,0.8406,0.1378 T0.8809,0.1779 L0.8813,0.1783 Q0.8813,0.1846,0.8778,0.1909 Q0.8636,0.2093,0.8299,0.2208 T0.7573,0.2343 T0.6769,0.2253 T0.6089,0.1913 L0.6067,0.2567 Q0.6315,0.281,0.6707,0.2928 T0.7489,0.3035 T0.8231,0.2903 T0.8764,0.258 L0.8778,0.2085 L0.9898,0.2685 L0.504,0.5352 L0.0177,0.2701 L0.1227,0.2118 L0.1213,0.2596 Q0.1457,0.2836,0.1851,0.2955 T0.2635,0.3062 T0.3377,0.293 T0.3911,0.2605 L0.3928,0.193 Q0.38,0.2106,0.3483,0.2221 T0.2792,0.2364 T0.2013,0.2303 T0.1324,0.2013 Q0.1222,0.1909,0.1222,0.1795 Q0.1222,0.156,0.1623,0.1393 T0.2591,0.1225 Q0.2679,0.1225,0.2817,0.1233 L0.3627,0.078 L0.3609,0.1359 Q0.3857,0.1598,0.4249,0.1718 T0.5031,0.1825 T0.5773,0.1693 T0.6306,0.1367 Z M0.6386,0.3242 Q0.6253,0.3427,0.5917,0.3544 T0.5186,0.3683 T0.4376,0.3595 T0.3689,0.3251 L0.3671,0.3909 Q0.3915,0.4148,0.4309,0.4268 T0.5093,0.4375 T0.5835,0.4243 T0.6368,0.3918 Z M0.0027,0.354 L0.4553,0.6091 L0.4553,0.9962 L0,0.7542 Z M1,0.3448 L1,0.7576 L0.5447,1 L0.5447,0.6128 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0D0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0.7699 Q0,0.7172,0.0277,0.6731 T0.103,0.6032 T0.2066,0.5774 Q0.2487,0.5774,0.2865,0.5922 T0.3526,0.6339 L0.3935,0.6715 Q0.4218,0.6984,0.438,0.7336 T0.4541,0.8075 Q0.4541,0.8602,0.4264,0.9046 T0.3511,0.9745 T0.2475,1 Q0.2054,1,0.1676,0.9852 T0.1016,0.9435 L0.0606,0.9059 Q0.0317,0.879,0.0159,0.8438 T0,0.7699 Z M0.5459,0.7699 Q0.5459,0.7172,0.5736,0.6731 T0.6489,0.6032 T0.7525,0.5774 Q0.7946,0.5774,0.8324,0.5922 T0.8984,0.6339 L0.9394,0.6715 Q0.9579,0.6892,0.9714,0.7105 T0.9925,0.7567 T1,0.8075 Q1,0.8602,0.9723,0.9046 T0.897,0.9745 T0.7934,1 Q0.7513,1,0.7135,0.9852 T0.6474,0.9435 L0.6065,0.9059 Q0.5776,0.879,0.5617,0.8438 T0.5459,0.7699 Z M0,0.1925 Q0,0.1398,0.0277,0.0957 T0.103,0.0258 T0.2066,0 Q0.2487,0,0.2865,0.0148 T0.3526,0.0565 L0.3935,0.0941 Q0.4218,0.121,0.438,0.1562 T0.4541,0.2301 Q0.4541,0.2828,0.4264,0.3269 T0.3511,0.3968 T0.2475,0.4226 Q0.2054,0.4226,0.1676,0.4078 T0.1016,0.3661 L0.0606,0.3285 Q0.0317,0.3016,0.0159,0.2664 T0,0.1925 Z M0.5459,0.1925 Q0.5459,0.1398,0.5736,0.0957 T0.6489,0.0258 T0.7525,0 Q0.7946,0,0.8324,0.0148 T0.8984,0.0565 L0.9394,0.0941 Q0.9677,0.121,0.9838,0.1562 T1,0.2301 Q1,0.2828,0.9723,0.3269 T0.897,0.3968 T0.7934,0.4226 Q0.7513,0.4226,0.7135,0.4078 T0.6474,0.3661 L0.6065,0.3285 Q0.5874,0.3108,0.5741,0.2895 T0.5534,0.2433 T0.5459,0.1925 Z M0.3301,0.6403 Q0.3306,0.6403,0.3312,0.6409 L0.3272,0.6371 L0.3272,0.6371 Z M0.8759,0.6403 Q0.8765,0.6403,0.8771,0.6409 Q0.8748,0.6387,0.8725,0.6371 L0.8731,0.6371 Z M0.3301,0.0629 Q0.3306,0.0629,0.3312,0.0634 L0.3272,0.0597 L0.3272,0.0602 Z M0.8759,0.0629 Q0.8765,0.0629,0.8771,0.0634 Q0.8748,0.0613,0.8725,0.0597 Q0.8731,0.0597,0.8731,0.0602 Z M0.779,0.4113 L0.7501,0.3849 Q0.7046,0.3844,0.6636,0.3661 Q0.7132,0.407,0.779,0.4113 Z M0.7957,0.4118 Q0.8477,0.4113,0.8918,0.3874 T0.9619,0.3223 T0.9885,0.2323 L0.9585,0.2048 Q0.9538,0.2769,0.8987,0.3282 T0.7657,0.3844 Z M0.9879,0.2167 Q0.9827,0.1554,0.9388,0.1097 Q0.9585,0.1478,0.959,0.1898 Z M0.2331,0.4113 L0.2043,0.3849 Q0.1587,0.3844,0.1177,0.3661 Q0.1673,0.407,0.2331,0.4113 Z M0.2499,0.4118 Q0.2885,0.4118,0.324,0.3973 T0.3852,0.3589 T0.4264,0.3019 T0.4426,0.2323 L0.4126,0.2048 Q0.408,0.2769,0.3529,0.3282 T0.2198,0.3844 Z M0.442,0.2167 Q0.4368,0.1554,0.393,0.1097 Q0.4126,0.1478,0.4132,0.1903 Z M0.779,0.9887 L0.7501,0.9624 Q0.7046,0.9618,0.6636,0.9435 Q0.7132,0.9844,0.779,0.9887 Z M0.7957,0.9892 Q0.8477,0.9887,0.8918,0.9648 T0.9619,0.8997 T0.9885,0.8097 L0.9585,0.7823 Q0.9538,0.8543,0.8987,0.9056 T0.7657,0.9618 Z M0.9879,0.7941 Q0.9827,0.7328,0.9388,0.6871 Q0.9585,0.7253,0.959,0.7672 Z M0.2331,0.9887 L0.2043,0.9624 Q0.1587,0.9618,0.1177,0.9435 Q0.1673,0.9844,0.2331,0.9887 Z M0.2499,0.9892 Q0.3018,0.9887,0.3459,0.9648 T0.416,0.8997 T0.4426,0.8102 L0.4126,0.7823 Q0.408,0.8543,0.3529,0.9056 T0.2198,0.9618 Z M0.442,0.7941 Q0.4368,0.7328,0.393,0.6871 Q0.4126,0.7253,0.4132,0.7672 Z M0.3191,0.6452 Q0.2943,0.622,0.2643,0.6091 T0.2066,0.5962 Q0.1795,0.5962,0.1497,0.6091 T0.0949,0.6441 T0.0537,0.6995 T0.0375,0.7699 Q0.0375,0.8419,0.0941,0.8941 Q0.1189,0.9172,0.1489,0.9304 T0.2066,0.9435 Q0.2337,0.9435,0.2634,0.9304 T0.3182,0.8952 T0.3595,0.8398 T0.3756,0.7699 Q0.3756,0.6978,0.3191,0.6452 Z M0.026,0.7731 L0.026,0.7699 Q0.026,0.6935,0.0854,0.6376 Q0.0658,0.6538,0.0514,0.6739 T0.0289,0.718 T0.0202,0.7677 Z M0.0271,0.7892 L0.0208,0.7833 Q0.0254,0.8419,0.0681,0.886 Q0.0664,0.8828,0.0646,0.879 Q0.0329,0.8382,0.0271,0.7892 Z M0.3866,0.7823 Q0.382,0.8511,0.3278,0.9016 Q0.3843,0.8565,0.3918,0.7871 Z M0.3872,0.7677 L0.393,0.7726 L0.393,0.7699 Q0.393,0.7349,0.3791,0.7038 Q0.3739,0.6973,0.3681,0.6914 Q0.3866,0.7274,0.3872,0.7677 Z M0.865,0.6452 Q0.8402,0.622,0.8102,0.6091 T0.7525,0.5962 Q0.7305,0.5962,0.7072,0.6043 T0.6619,0.628 T0.6223,0.6642 T0.5941,0.7124 T0.5834,0.7699 Q0.5834,0.8419,0.6399,0.8941 Q0.6561,0.9097,0.6757,0.9207 T0.7149,0.9376 T0.7525,0.9435 Q0.7796,0.9435,0.8093,0.9304 T0.8641,0.8952 T0.9054,0.8398 T0.9215,0.7699 Q0.9215,0.6978,0.865,0.6452 Z M0.5718,0.7731 L0.5718,0.7699 Q0.5718,0.6935,0.6313,0.6376 Q0.6013,0.6618,0.584,0.6954 T0.5661,0.7677 Z M0.573,0.7892 L0.5666,0.7833 Q0.5713,0.8419,0.614,0.886 Q0.6122,0.8828,0.6105,0.879 Q0.5788,0.8382,0.573,0.7892 Z M0.9325,0.7823 Q0.9279,0.8511,0.8736,0.9016 Q0.9308,0.8565,0.9377,0.7871 Z M0.9331,0.7672 L0.9388,0.7726 L0.9388,0.7699 Q0.9388,0.7349,0.925,0.7038 Q0.9198,0.6973,0.914,0.6914 Q0.9325,0.7274,0.9331,0.7672 Z M0.3191,0.0677 Q0.2943,0.0446,0.2643,0.0317 T0.2066,0.0188 Q0.1795,0.0188,0.1497,0.0317 T0.0949,0.0667 T0.0537,0.122 T0.0375,0.1925 Q0.0375,0.2645,0.0941,0.3167 Q0.1189,0.3398,0.1489,0.353 T0.2066,0.3661 Q0.2337,0.3661,0.2634,0.353 T0.3182,0.3177 T0.3595,0.2624 T0.3756,0.1925 Q0.3756,0.1204,0.3191,0.0677 Z M0.026,0.1957 L0.026,0.1925 Q0.026,0.1161,0.0854,0.0602 Q0.0554,0.0844,0.0381,0.118 T0.0202,0.1903 Z M0.0271,0.2118 L0.0208,0.2059 Q0.0254,0.2645,0.0681,0.3086 Q0.0664,0.3054,0.0646,0.3016 Q0.0329,0.2608,0.0271,0.2118 Z M0.3866,0.2048 Q0.382,0.2737,0.3278,0.3242 Q0.3849,0.279,0.3918,0.2097 Z M0.3872,0.1903 L0.393,0.1952 L0.393,0.1925 Q0.393,0.1581,0.3791,0.1263 Q0.3739,0.1199,0.3681,0.114 Q0.3866,0.15,0.3872,0.1903 Z M0.865,0.0677 Q0.8402,0.0446,0.8102,0.0317 T0.7525,0.0188 Q0.7253,0.0188,0.6956,0.0317 T0.6408,0.0667 T0.5995,0.122 T0.5834,0.1925 Q0.5834,0.2645,0.6399,0.3167 Q0.6647,0.3398,0.6947,0.353 T0.7525,0.3661 Q0.7796,0.3661,0.8093,0.353 T0.8641,0.3177 T0.9054,0.2624 T0.9215,0.1925 Q0.9215,0.1204,0.865,0.0677 Z M0.5718,0.1957 L0.5718,0.1925 Q0.5718,0.1161,0.6313,0.0602 Q0.6013,0.0844,0.584,0.118 T0.5661,0.1903 Z M0.573,0.2118 L0.5666,0.2059 Q0.5713,0.2645,0.614,0.3086 Q0.6122,0.3054,0.6105,0.3016 Q0.5788,0.2608,0.573,0.2118 Z M0.9325,0.2048 Q0.9279,0.2737,0.8736,0.3242 Q0.9308,0.279,0.9377,0.2097 Z M0.9331,0.1898 L0.9388,0.1952 L0.9388,0.1925 Q0.9388,0.1575,0.925,0.1263 Q0.9198,0.1199,0.914,0.114 Q0.9325,0.15,0.9331,0.1898 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0D1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.7639,0.1974 L0.9593,0.1974 L0.9593,0.1443 L0.7639,0.1443 L0.7639,0.1974 Z M0.7586,0.109 L0.9727,0.109 Q0.9839,0.109,0.992,0.1153 T1,0.1291 L1,0.382 Q1,0.3906,0.9912,0.3964 T0.9727,0.4021 L0.7586,0.4021 Q0.7468,0.4021,0.739,0.3956 T0.7313,0.382 L0.7313,0.1291 Q0.7313,0.1246,0.7339,0.1207 T0.7406,0.1143 T0.7495,0.1104 T0.7586,0.109 Z M0.7639,0.6575 L0.9593,0.6575 L0.9593,0.6044 L0.7639,0.6044 L0.7639,0.6575 Z M0.7313,0.8347 L0.7313,0.5843 Q0.7313,0.5757,0.7401,0.5699 T0.7586,0.5641 L0.9706,0.5641 Q0.9818,0.5641,0.9898,0.5705 T0.9979,0.5843 L0.9979,0.8347 Q0.9979,0.8433,0.989,0.8491 T0.9706,0.8549 L0.7586,0.8549 Q0.7468,0.8549,0.739,0.8483 T0.7313,0.8347 Z M0,1 L0,0 L0.7607,0 L0.7607,1 L0,1 Z', type: CustomGlyphVectorType.FILL } },\n  '\\u{E0D2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.9764,0.0029 L0.3179,0.4552 L0,0.4552 L0,0 Z M1,1 L0,1 L0,0.5448 L0.3091,0.5448 Z', type: CustomGlyphVectorType.FILL } },\n  // '\\u{E0C3}' is unused\n  '\\u{E0D4}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.0236,0.0029 L0.6824,0.4552 L1,0.4552 L1,0 Z M0,1 L1,1 L1,0.5448 L0.6912,0.5448 Z', type: CustomGlyphVectorType.FILL } },\n\n  // #endregion\n\n  // #region Progress Indicators (EE00-EE0B)\n\n  // initially added in Fira Code and later Nerd Fonts\n  // https://github.com/ryanoasis/nerd-fonts/pull/1733\n\n  // Progress bars (EE00-EE05)\n  '\\u{EE00}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY START\n  '\\u{EE01}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY MIDDLE\n  '\\u{EE02}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z' }, // PROGRESS BAR EMPTY END\n  '\\u{EE03}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z' }, // PROGRESS BAR FILLED START\n  '\\u{EE04}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z' }, // PROGRESS BAR FILLED MIDDLE\n  '\\u{EE05}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z' }, // PROGRESS BAR FILLED END\n\n  // Progress spinners (EE06-EE0B) - 6-frame spinner animation using stroked arcs\n  '\\u{EE06}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 1 (12 o'clock)\n  '\\u{EE07}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 2 (2 o'clock)\n  '\\u{EE08}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 3 (4 o'clock)\n  '\\u{EE09}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 4 (6 o'clock)\n  '\\u{EE0A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 5 (8 o'clock)\n  '\\u{EE0B}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 6 (10 o'clock)\n\n  // #endregion\n\n  // #region Git Branch Symbols (F5D0-F60D)\n\n  // Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)\n  // then in Wezterm (https://github.com/wezterm/wezterm/issues/6328).\n\n  // Straight lines (F5D0-F5D9)\n  '\\u{F5D0}': GitBranchSymbolsParts.LINE_H, // Same as 2500\n  '\\u{F5D1}': GitBranchSymbolsParts.LINE_V, // Same as 2502\n  '\\u{F5D2}': GitBranchSymbolsParts.FADE_RIGHT,\n  '\\u{F5D3}': GitBranchSymbolsParts.FADE_LEFT,\n  '\\u{F5D4}': GitBranchSymbolsParts.FADE_DOWN,\n  '\\u{F5D5}': GitBranchSymbolsParts.FADE_UP,\n\n  // Curved lines (F5D6-F5D9)\n  '\\u{F5D6}': GitBranchSymbolsParts.CURVE_DOWN_RIGHT, // Same as 256D)\n  '\\u{F5D7}': GitBranchSymbolsParts.CURVE_DOWN_LEFT, // Same as 256E)\n  '\\u{F5D8}': GitBranchSymbolsParts.CURVE_UP_RIGHT, // Same as 2570)\n  '\\u{F5D9}': GitBranchSymbolsParts.CURVE_UP_LEFT, // Same as 256F)\n\n  // Branching lines (F5DA-F5ED)\n  '\\u{F5DA}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5DB}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],\n  '\\u{F5DC}': [GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5DD}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_UP_LEFT],\n  '\\u{F5DE}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_LEFT],\n  '\\u{F5DF}': [GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_LEFT],\n  '\\u{F5E0}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_LEFT],\n  '\\u{F5E1}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],\n  '\\u{F5E2}': [GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],\n  '\\u{F5E3}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_UP_LEFT],\n  '\\u{F5E4}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5E5}': [GitBranchSymbolsParts.CURVE_UP_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5E6}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_UP_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5E7}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],\n  '\\u{F5E8}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_LEFT],\n  '\\u{F5E9}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5EA}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_LEFT],\n  '\\u{F5EB}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n  '\\u{F5EC}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_LEFT],\n  '\\u{F5ED}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],\n\n  // Nodes (F5EE-F5FB)\n  '\\u{F5EE}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE],\n  '\\u{F5EF}': GitBranchSymbolsParts.NODE_STROKE,\n  '\\u{F5F0}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F5F1}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F5F2}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F5F3}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F5F4}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F5F5}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F5F6}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN],\n  '\\u{F5F7}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN],\n  '\\u{F5F8}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP],\n  '\\u{F5F9}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP],\n  '\\u{F5FA}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],\n  '\\u{F5FB}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],\n\n  // Extended Nodes (F5FC-F60D)\n  // These were added a little later https://github.com/kovidgoyal/kitty/pull/7805\n  '\\u{F5FC}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F5FD}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F5FE}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F5FF}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F600}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F601}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F602}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F603}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F604}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F605}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F606}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F607}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],\n  '\\u{F608}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F609}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F60A}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F60B}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F60C}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n  '\\u{F60D}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],\n\n  // #endregion\n\n  // #region Symbols for Legacy Computing (1FB00-1FB3B)\n\n  // https://www.unicode.org/charts/PDF/U1FB00.pdf\n\n  // Block mosaic terminal graphic characters (1FB00-1FB3B)\n  // The term \"sextant\" refers to block mosaics divided into six parts.\n  '\\u{1FB00}': sextant(0b000001), // BLOCK SEXTANT-1\n  '\\u{1FB01}': sextant(0b000010), // BLOCK SEXTANT-2\n  '\\u{1FB02}': sextant(0b000011), // BLOCK SEXTANT-12 (upper one third block)\n  '\\u{1FB03}': sextant(0b000100), // BLOCK SEXTANT-3\n  '\\u{1FB04}': sextant(0b000101), // BLOCK SEXTANT-13\n  '\\u{1FB05}': sextant(0b000110), // BLOCK SEXTANT-23\n  '\\u{1FB06}': sextant(0b000111), // BLOCK SEXTANT-123\n  '\\u{1FB07}': sextant(0b001000), // BLOCK SEXTANT-4\n  '\\u{1FB08}': sextant(0b001001), // BLOCK SEXTANT-14\n  '\\u{1FB09}': sextant(0b001010), // BLOCK SEXTANT-24\n  '\\u{1FB0A}': sextant(0b001011), // BLOCK SEXTANT-124\n  '\\u{1FB0B}': sextant(0b001100), // BLOCK SEXTANT-34 (middle one third block)\n  '\\u{1FB0C}': sextant(0b001101), // BLOCK SEXTANT-134\n  '\\u{1FB0D}': sextant(0b001110), // BLOCK SEXTANT-234\n  '\\u{1FB0E}': sextant(0b001111), // BLOCK SEXTANT-1234 (upper two thirds block)\n  '\\u{1FB0F}': sextant(0b010000), // BLOCK SEXTANT-5\n  '\\u{1FB10}': sextant(0b010001), // BLOCK SEXTANT-15\n  '\\u{1FB11}': sextant(0b010010), // BLOCK SEXTANT-25\n  '\\u{1FB12}': sextant(0b010011), // BLOCK SEXTANT-125\n  '\\u{1FB13}': sextant(0b010100), // BLOCK SEXTANT-35\n  '\\u{1FB14}': sextant(0b010110), // BLOCK SEXTANT-235\n  '\\u{1FB15}': sextant(0b010111), // BLOCK SEXTANT-1235\n  '\\u{1FB16}': sextant(0b011000), // BLOCK SEXTANT-45\n  '\\u{1FB17}': sextant(0b011001), // BLOCK SEXTANT-145\n  '\\u{1FB18}': sextant(0b011010), // BLOCK SEXTANT-245\n  '\\u{1FB19}': sextant(0b011011), // BLOCK SEXTANT-1245\n  '\\u{1FB1A}': sextant(0b011100), // BLOCK SEXTANT-345\n  '\\u{1FB1B}': sextant(0b011101), // BLOCK SEXTANT-1345\n  '\\u{1FB1C}': sextant(0b011110), // BLOCK SEXTANT-2345\n  '\\u{1FB1D}': sextant(0b011111), // BLOCK SEXTANT-12345\n  '\\u{1FB1E}': sextant(0b100000), // BLOCK SEXTANT-6\n  '\\u{1FB1F}': sextant(0b100001), // BLOCK SEXTANT-16\n  '\\u{1FB20}': sextant(0b100010), // BLOCK SEXTANT-26\n  '\\u{1FB21}': sextant(0b100011), // BLOCK SEXTANT-126\n  '\\u{1FB22}': sextant(0b100100), // BLOCK SEXTANT-36\n  '\\u{1FB23}': sextant(0b100101), // BLOCK SEXTANT-136\n  '\\u{1FB24}': sextant(0b100110), // BLOCK SEXTANT-236\n  '\\u{1FB25}': sextant(0b100111), // BLOCK SEXTANT-1236\n  '\\u{1FB26}': sextant(0b101000), // BLOCK SEXTANT-46\n  '\\u{1FB27}': sextant(0b101001), // BLOCK SEXTANT-146\n  '\\u{1FB28}': sextant(0b101011), // BLOCK SEXTANT-1246\n  '\\u{1FB29}': sextant(0b101100), // BLOCK SEXTANT-346\n  '\\u{1FB2A}': sextant(0b101101), // BLOCK SEXTANT-1346\n  '\\u{1FB2B}': sextant(0b101110), // BLOCK SEXTANT-2346\n  '\\u{1FB2C}': sextant(0b101111), // BLOCK SEXTANT-12346\n  '\\u{1FB2D}': sextant(0b110000), // BLOCK SEXTANT-56 (lower one third block)\n  '\\u{1FB2E}': sextant(0b110001), // BLOCK SEXTANT-156\n  '\\u{1FB2F}': sextant(0b110010), // BLOCK SEXTANT-256\n  '\\u{1FB30}': sextant(0b110011), // BLOCK SEXTANT-1256 (upper and lower one third block)\n  '\\u{1FB31}': sextant(0b110100), // BLOCK SEXTANT-356\n  '\\u{1FB32}': sextant(0b110101), // BLOCK SEXTANT-1356\n  '\\u{1FB33}': sextant(0b110110), // BLOCK SEXTANT-2356\n  '\\u{1FB34}': sextant(0b110111), // BLOCK SEXTANT-12356\n  '\\u{1FB35}': sextant(0b111000), // BLOCK SEXTANT-456\n  '\\u{1FB36}': sextant(0b111001), // BLOCK SEXTANT-1456\n  '\\u{1FB37}': sextant(0b111010), // BLOCK SEXTANT-2456\n  '\\u{1FB38}': sextant(0b111011), // BLOCK SEXTANT-12456\n  '\\u{1FB39}': sextant(0b111100), // BLOCK SEXTANT-3456 (lower two thirds block)\n  '\\u{1FB3A}': sextant(0b111101), // BLOCK SEXTANT-13456\n  '\\u{1FB3B}': sextant(0b111110), // BLOCK SEXTANT-23456\n\n  // Smooth mosaic terminal graphic characters (1FB3C-1FB6F)\n  '\\u{1FB3C}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0,1 L0.5,1 Z' },           // LOWER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FB3D}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0,1 L1,1 Z' },             // LOWER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER RIGHT\n  '\\u{1FB3E}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0,1 L0.5,1 Z' },           // LOWER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FB3F}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0,1 L1,1 Z' },             // LOWER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER RIGHT\n  '\\u{1FB40}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0,1 L0.5,1 Z' },                // LOWER LEFT BLOCK DIAGONAL UPPER LEFT TO LOWER CENTRE\n  '\\u{1FB41}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0.5,0 L1,0 L1,1 L0,1 Z' }, // LOWER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER CENTRE\n  '\\u{1FB42}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L1,0 L1,1 L0,1 Z' },        // LOWER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER RIGHT\n  '\\u{1FB43}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0.5,0 L1,0 L1,1 L0,1 Z' }, // LOWER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER CENTRE\n  '\\u{1FB44}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L1,0 L1,1 L0,1 Z' },        // LOWER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER RIGHT\n  '\\u{1FB45}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L0.5,0 L1,0 L1,1 Z' },           // LOWER RIGHT BLOCK DIAGONAL LOWER LEFT TO UPPER CENTRE\n  '\\u{1FB46}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L1,0.3333 L1,1 L0,1 Z' },   // LOWER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER MIDDLE RIGHT\n  '\\u{1FB47}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,1 L1,0.6667 L1,1 Z' },           // LOWER RIGHT BLOCK DIAGONAL LOWER CENTRE TO LOWER MIDDLE RIGHT\n  '\\u{1FB48}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L1,0.6667 L1,1 Z' },             // LOWER RIGHT BLOCK DIAGONAL LOWER LEFT TO LOWER MIDDLE RIGHT\n  '\\u{1FB49}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,1 L1,0.3333 L1,1 Z' },           // LOWER RIGHT BLOCK DIAGONAL LOWER CENTRE TO UPPER MIDDLE RIGHT\n  '\\u{1FB4A}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L1,0.3333 L1,1 Z' },             // LOWER RIGHT BLOCK DIAGONAL LOWER LEFT TO UPPER MIDDLE RIGHT\n  '\\u{1FB4B}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,1 L1,0 L1,1 Z' },                // LOWER RIGHT BLOCK DIAGONAL LOWER CENTRE TO UPPER RIGHT\n  '\\u{1FB4C}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,0 L0,0 L0,1 L1,1 L1,0.3333 Z' }, // LOWER LEFT BLOCK DIAGONAL UPPER CENTRE TO UPPER MIDDLE RIGHT\n  '\\u{1FB4D}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0,1 L1,1 L1,0.3333 Z' },        // LOWER LEFT BLOCK DIAGONAL UPPER LEFT TO UPPER MIDDLE RIGHT\n  '\\u{1FB4E}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,0 L0,0 L0,1 L1,1 L1,0.6667 Z' }, // LOWER LEFT BLOCK DIAGONAL UPPER CENTRE TO LOWER MIDDLE RIGHT\n  '\\u{1FB4F}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0,1 L1,1 L1,0.6667 Z' },        // LOWER LEFT BLOCK DIAGONAL UPPER LEFT TO LOWER MIDDLE RIGHT\n  '\\u{1FB50}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,0 L0,0 L0,1 L1,1 Z' },           // LOWER LEFT BLOCK DIAGONAL UPPER CENTRE TO LOWER RIGHT\n  '\\u{1FB51}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0,1 L1,1 L1,0.6667 Z' },   // LOWER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER MIDDLE RIGHT\n  '\\u{1FB52}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0.5,1 L1,1 L1,0 L0,0 Z' }, // UPPER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FB53}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L1,1 L1,0 L0,0 Z' },        // UPPER RIGHT BLOCK DIAGONAL LOWER MIDDLE LEFT TO LOWER RIGHT\n  '\\u{1FB54}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0.5,1 L1,1 L1,0 L0,0 Z' }, // UPPER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FB55}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L1,1 L1,0 L0,0 Z' },        // UPPER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER RIGHT\n  '\\u{1FB56}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.5,1 L1,1 L1,0 Z' },           // UPPER RIGHT BLOCK DIAGONAL UPPER LEFT TO LOWER CENTRE\n  '\\u{1FB57}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0,0 L0.5,0 Z' },           // UPPER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER CENTRE\n  '\\u{1FB58}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L0,0 L1,0 Z' },             // UPPER LEFT BLOCK DIAGONAL UPPER MIDDLE LEFT TO UPPER RIGHT\n  '\\u{1FB59}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0,0 L0.5,0 Z' },           // UPPER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER CENTRE\n  '\\u{1FB5A}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0,0 L1,0 Z' },             // UPPER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER RIGHT\n  '\\u{1FB5B}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L0,0 L0.5,0 Z' },                // UPPER LEFT BLOCK DIAGONAL LOWER LEFT TO UPPER CENTRE\n  '\\u{1FB5C}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.6667 L0,0 L1,0 L1,0.3333 Z' },   // UPPER LEFT BLOCK DIAGONAL LOWER MIDDLE LEFT TO UPPER MIDDLE RIGHT\n  '\\u{1FB5D}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,1 L0,1 L0,0 L1,0 L1,0.6667 Z' }, // UPPER LEFT BLOCK DIAGONAL LOWER CENTRE TO LOWER MIDDLE RIGHT\n  '\\u{1FB5E}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L0,0 L1,0 L1,0.6667 Z' },        // UPPER LEFT BLOCK DIAGONAL LOWER LEFT TO LOWER MIDDLE RIGHT\n  '\\u{1FB5F}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,1 L0,1 L0,0 L1,0 L1,0.3333 Z' }, // UPPER LEFT BLOCK DIAGONAL LOWER CENTRE TO UPPER MIDDLE RIGHT\n  '\\u{1FB60}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L0,0 L1,0 L1,0.3333 Z' },        // UPPER LEFT BLOCK DIAGONAL LOWER LEFT TO UPPER MIDDLE RIGHT\n  '\\u{1FB61}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,1 L0,1 L0,0 L1,0 Z' },           // UPPER LEFT BLOCK DIAGONAL LOWER CENTRE TO UPPER RIGHT\n  '\\u{1FB62}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,0 L1,0 L1,0.3333 Z' },           // UPPER RIGHT BLOCK DIAGONAL UPPER CENTRE TO UPPER MIDDLE RIGHT\n  '\\u{1FB63}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,0.3333 Z' },             // UPPER RIGHT BLOCK DIAGONAL UPPER LEFT TO UPPER MIDDLE RIGHT\n  '\\u{1FB64}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,0 L1,0 L1,0.6667 Z' },           // UPPER RIGHT BLOCK DIAGONAL UPPER CENTRE TO LOWER MIDDLE RIGHT\n  '\\u{1FB65}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,0.6667 Z' },             // UPPER RIGHT BLOCK DIAGONAL UPPER LEFT TO LOWER MIDDLE RIGHT\n  '\\u{1FB66}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.5,0 L1,0 L1,1 Z' },                // UPPER RIGHT BLOCK DIAGONAL UPPER CENTRE TO LOWER RIGHT\n  '\\u{1FB67}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.3333 L1,0.6667 L1,0 L0,0 Z' },   // UPPER RIGHT BLOCK DIAGONAL UPPER MIDDLE LEFT TO LOWER MIDDLE RIGHT\n  '\\u{1FB68}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,1 L0,1 L0.5,0.5 Z' },    // UPPER AND RIGHT AND LOWER TRIANGULAR THREE QUARTERS BLOCK (missing left)\n  '\\u{1FB69}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.5,0.5 L1,0 L1,1 L0,1 Z' },    // LEFT AND LOWER AND RIGHT TRIANGULAR THREE QUARTERS BLOCK (missing upper)\n  '\\u{1FB6A}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L0.5,0.5 L1,1 L0,1 Z' },    // UPPER AND LEFT AND LOWER TRIANGULAR THREE QUARTERS BLOCK (missing right)\n  '\\u{1FB6B}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,1 L0.5,0.5 L0,1 Z' },    // LEFT AND UPPER AND RIGHT TRIANGULAR THREE QUARTERS BLOCK (missing lower)\n  '\\u{1FB6C}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.5,0.5 L0,1 Z' },              // LEFT TRIANGULAR ONE QUARTER BLOCK\n  '\\u{1FB6D}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L0.5,0.5 Z' },              // UPPER TRIANGULAR ONE QUARTER BLOCK\n  '\\u{1FB6E}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L1,1 L0.5,0.5 Z' },              // RIGHT TRIANGULAR ONE QUARTER BLOCK\n  '\\u{1FB6F}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L1,1 L0.5,0.5 Z' },              // LOWER TRIANGULAR ONE QUARTER BLOCK\n\n  // Block elements (1FB70-1FB80)\n  '\\u{1FB70}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 1, y: 0, w: 1, h: 8 }] },                             // VERTICAL ONE EIGHTH BLOCK-2\n  '\\u{1FB71}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 0, w: 1, h: 8 }] },                             // VERTICAL ONE EIGHTH BLOCK-3\n  '\\u{1FB72}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 3, y: 0, w: 1, h: 8 }] },                             // VERTICAL ONE EIGHTH BLOCK-4\n  '\\u{1FB73}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 1, h: 8 }] },                             // VERTICAL ONE EIGHTH BLOCK-5\n  '\\u{1FB74}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 5, y: 0, w: 1, h: 8 }] },                             // VERTICAL ONE EIGHTH BLOCK-6\n  '\\u{1FB75}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 6, y: 0, w: 1, h: 8 }] },                             // VERTICAL ONE EIGHTH BLOCK-7\n  '\\u{1FB76}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 1, w: 8, h: 1 }] },                             // HORIZONTAL ONE EIGHTH BLOCK-2\n  '\\u{1FB77}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 2, w: 8, h: 1 }] },                             // HORIZONTAL ONE EIGHTH BLOCK-3\n  '\\u{1FB78}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 3, w: 8, h: 1 }] },                             // HORIZONTAL ONE EIGHTH BLOCK-4\n  '\\u{1FB79}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 4, w: 8, h: 1 }] },                             // HORIZONTAL ONE EIGHTH BLOCK-5\n  '\\u{1FB7A}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 5, w: 8, h: 1 }] },                             // HORIZONTAL ONE EIGHTH BLOCK-6\n  '\\u{1FB7B}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 6, w: 8, h: 1 }] },                             // HORIZONTAL ONE EIGHTH BLOCK-7\n  '\\u{1FB7C}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }] }, // LEFT AND LOWER ONE EIGHTH BLOCK\n  '\\u{1FB7D}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }] }, // LEFT AND UPPER ONE EIGHTH BLOCK\n  '\\u{1FB7E}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }] }, // RIGHT AND UPPER ONE EIGHTH BLOCK\n  '\\u{1FB7F}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }] }, // RIGHT AND LOWER ONE EIGHTH BLOCK\n  '\\u{1FB80}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }] }, // UPPER AND LOWER ONE EIGHTH BLOCK\n\n  // Window title bar (1FB81-1FB81)\n  '\\u{1FB81}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-1358\n\n  // Block elements (1FB82-1FB8B)\n  '\\u{1FB82}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 2 }] }, // UPPER ONE QUARTER BLOCK\n  '\\u{1FB83}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 3 }] }, // UPPER THREE EIGHTHS BLOCK\n  '\\u{1FB84}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 5 }] }, // UPPER FIVE EIGHTHS BLOCK\n  '\\u{1FB85}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 6 }] }, // UPPER THREE QUARTERS BLOCK\n  '\\u{1FB86}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 7 }] }, // UPPER SEVEN EIGHTHS BLOCK\n  '\\u{1FB87}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 6, y: 0, w: 2, h: 8 }] }, // RIGHT ONE QUARTER BLOCK\n  '\\u{1FB88}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 5, y: 0, w: 3, h: 8 }] }, // RIGHT THREE EIGHTHS B0OCK\n  '\\u{1FB89}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 3, y: 0, w: 5, h: 8 }] }, // RIGHT FIVE EIGHTHS BL0CK\n  '\\u{1FB8A}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 0, w: 6, h: 8 }] }, // RIGHT THREE QUARTERS 0LOCK\n  '\\u{1FB8B}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 1, y: 0, w: 7, h: 8 }] }, // RIGHT SEVEN EIGHTHS B0OCK\n\n  // Rectangular shade characters (1FB8C-1FB94)\n  '\\u{1FB8C}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // LEFT HALF MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0,0 L0.5,0 L0.5,1 L0,1 Z' },\n  '\\u{1FB8D}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [  // RIGHT HALF MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0.5,0 L1,0 L1,1 L0.5,1 Z' },\n  '\\u{1FB8E}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [  // UPPER HALF MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0,0 L1,0 L1,0.5 L0,0.5 Z' },\n  '\\u{1FB8F}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [  // LOWER HALF MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0,0.5 L1,0.5 L1,1 L0,1 Z' },\n  '\\u{1FB90}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [  // INVERSE MEDIUM SHADE\n    [0, 1],\n    [1, 0]\n  ] },\n  '\\u{1FB91}': [ // UPPER HALF BLOCK AND LOWER HALF INVERSE MEDIUM SHADE\n    { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [\n      [0, 1],\n      [1, 0]\n    ], clipPath: 'M0,0.5 L1,0.5 L1,1 L0,1 Z' },\n    { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 8, h: 4 }] }\n  ],\n  '\\u{1FB92}': [ // UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK\n    { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [\n      [0, 1],\n      [1, 0]\n    ], clipPath: 'M0,0 L1,0 L1,0.5 L0,0.5 Z' },\n    { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 4, w: 8, h: 4 }] }\n  ],\n  // 1FB93 is <reserved>\n  '\\u{1FB94}': [ // LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK\n    { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [\n      [0, 1],\n      [1, 0]\n    ], clipPath: 'M0,0 L0.5,0 L0.5,1 L0,1 Z' },\n    { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 4, h: 8 }] }\n  ],\n\n  // Fill characters (1FB95-1FB97)\n  '\\u{1FB95}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [ // CHECKER BOARD FILL\n    { x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 },\n    { x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 },\n    { x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 },\n    { x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 }\n  ] },\n  '\\u{1FB96}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [ // INVERSE CHECKER BOARD FILL\n    { x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 },\n    { x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 },\n    { x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 },\n    { x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 }\n  ] },\n  '\\u{1FB97}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [ // HEAVY HORIZONTAL FILL (upper middle and lower one quarter block)\n    { x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }\n  ] },\n\n  // Diagonal fill characters (1FB98-1FB99)\n  '\\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL\n  '\\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL\n\n  // Smooth mosaic terminal graphic characters (1FB9A-1FB9B)\n  '\\u{1FB9A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0', type: CustomGlyphVectorType.FILL } }, // UPPER AND LOWER TRIANGULAR HALF BLOCK\n  '\\u{1FB9B}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1', type: CustomGlyphVectorType.FILL } }, // LEFT AND RIGHT TRIANGULAR HALF BLOCK\n\n  // Triangular shade characters (1FB9C-1FB9F)\n  '\\u{1FB9C}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // UPPER LEFT TRIANGULAR MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0,0 L1,0 L0,1 Z' },\n  '\\u{1FB9D}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // UPPER RIGHT TRIANGULAR MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0,0 L1,0 L1,1 Z' },\n  '\\u{1FB9E}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // LOWER RIGHT TRIANGULAR MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M1,0 L1,1 L0,1 Z' },\n  '\\u{1FB9F}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: [ // LOWER LEFT TRIANGULAR MEDIUM SHADE\n    [1, 0],\n    [0, 1]\n  ], clipPath: 'M0,0 L1,1 L0,1 Z' },\n\n  // Character cell diagonals (1FBA0-1FBAE)\n  '\\u{1FBA0}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L0,.5', strokeWidth: 1 },               // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT\n  '\\u{1FBA1}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L1,.5', strokeWidth: 1 },               // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT\n  '\\u{1FBA2}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L.5,1', strokeWidth: 1 },               // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FBA3}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,.5 L.5,1', strokeWidth: 1 },               // BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO LOWER CENTRE\n  '\\u{1FBA4}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L0,.5 L.5,1', strokeWidth: 1 },         // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FBA5}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L1,.5 L.5,1', strokeWidth: 1 },         // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE\n  '\\u{1FBA6}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L.5,1 L1,.5', strokeWidth: 1 },         // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER CENTRE TO MIDDLE RIGHT\n  '\\u{1FBA7}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L.5,0 L1,.5', strokeWidth: 1 },         // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO UPPER CENTRE TO MIDDLE RIGHT\n  '\\u{1FBA8}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L0,.5 M1,.5 L.5,1', strokeWidth: 1 },   // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT AND MIDDLE RIGHT TO LOWER CENTRE\n  '\\u{1FBA9}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L1,.5 M0,.5 L.5,1', strokeWidth: 1 },   // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT AND MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FBAA}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L1,.5 L.5,1 L0,.5', strokeWidth: 1 },   // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE TO MIDDLE LEFT\n  '\\u{1FBAB}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L0,.5 L.5,1 L1,.5', strokeWidth: 1 },   // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE TO MIDDLE RIGHT\n  '\\u{1FBAC}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L.5,0 L1,.5 L.5,1', strokeWidth: 1 },   // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE\n  '\\u{1FBAD}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,.5 L.5,0 L0,.5 L.5,1', strokeWidth: 1 },   // BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE\n  '\\u{1FBAE}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L1,.5 L.5,1 L0,.5 Z', strokeWidth: 1 }, // BOX DRAWINGS LIGHT DIAGONAL DIAMOND\n\n  // Light solid line with stroke (1FBAF-1FBAF)\n  '\\u{1FBAF}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: `${Shapes.LEFT_TO_RIGHT} M.5,.35 L.5,.65`, strokeWidth: 1 }, // BOX DRAWINGS LIGHT HORIZONTAL WITH VERTICAL STROKE\n\n  // Terminal graphic characters (1FBB0-1FBB3)\n  '\\u{1FBB0}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z', scaleType: CustomGlyphScaleType.CHAR },                                           // ARROWHEAD-SHAPED POINTER\n  '\\u{1FBB1}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.1,.525 L.35,.675 L.9,.35', type: CustomGlyphVectorType.STROKE }, scaleType: CustomGlyphScaleType.CHAR }, // INVERSE CHECK MARK\n  '\\u{1FBB2}': { type: CustomGlyphDefinitionType.PATH, data: 'M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13' }, // LEFT HALF RUNNING MAN\n  '\\u{1FBB3}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6' },                                                                                                      // RIGHT HALF RUNNING MAN\n\n  // Arrows (1FBB4-1FBB8)\n  // TODO: Improve all arrows, use hybrid approach\n  '\\u{1FBB4}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR },                                        // INVERSE DOWNWARDS ARROW WITH TIP LEFTWARDS\n  '\\u{1FBB5}': [ // LEFTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z', type: CustomGlyphVectorType.FILL } },\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.15,.5 L.5,.35 L.5,.425 L.85,.425 L.85,.575 L.5,.575 L.5,.65 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }\n  ],\n  '\\u{1FBB6}': [ // RIGHTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z', type: CustomGlyphVectorType.FILL } },\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.85,.5 L.5,.35 L.5,.425 L.15,.425 L.15,.575 L.5,.575 L.5,.65 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }\n  ],\n  '\\u{1FBB7}': [ // DOWNWARDS ARROW AND RIGHT ONE EIGHTH BLOCK\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.875,0 L1,0 L1,1 L.875,1 Z', type: CustomGlyphVectorType.FILL } },\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.5,.675 L.2,.5 L.35,.5 L.35,.325 L.65,.325 L.65,.5 L.8,.5 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }\n  ],\n  '\\u{1FBB8}': [ // UPWARDS ARROW AND RIGHT ONE EIGHTH BLOCK\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.875,0 L1,0 L1,1 L.875,1 Z', type: CustomGlyphVectorType.FILL } },\n    { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.5,.325 L.2,.5 L.35,.5 L.35,.675 L.65,.675 L.65,.5 L.8,.5 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }\n  ],\n\n  // Terminal graphic characters (1FBB9-1FBBC)\n  '\\u{1FBB9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.405 L.92,.405 L.69,.185 L.41,.185 L.21,.42 L.21,.825 L1,.825 Z', type: CustomGlyphVectorType.FILL } }, // LEFT HALF FOLDER\n  '\\u{1FBBA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,.89 L0,.825 L.78,.825 L.78,.53 L.7,.415 L0,.415 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z', type: CustomGlyphVectorType.FILL } }, // RIGHT HALF FOLDER\n  '\\u{1FBBB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.31,.275 L.44,.275 L.44,.47 L.05,.47 L.05,.405 L.31,.405 Z M.56,.275 L.69,.275 L.69,.405 L.95,.405 L.95,.47 L.56,.47 Z M.05,.53 L.44,.53 L.44,.725 L.31,.725 L.31,.595 L.05,.595 Z M.56,.53 L.95,.53 L.95,.595 L.69,.595 L.69,.725 L.56,.725 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // VOIDED GREEK CROSS\n  '\\u{1FBBC}': [ // RIGHT OPEN SQUARED DOT\n    { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,1 L0,1 L0,.935 L.885,.935 L.885,.065 L0,.065 Z' },\n    { type: CustomGlyphDefinitionType.PATH, data: 'M.67,.5 A.17,.085,0,1,1,.33,.5 A.17,.085,0,1,1,.67,.5', scaleType: CustomGlyphScaleType.CHAR }\n  ],\n\n  // Negative terminal graphic characters (1FBBD-1FBBF)\n  '\\u{1FBBD}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z', type: CustomGlyphVectorType.STROKE } }, // NEGATIVE DIAGONAL CROSS\n  '\\u{1FBBE}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M1,.5 L.5,1', type: CustomGlyphVectorType.STROKE } },                         // NEGATIVE DIAGONAL MIDDLE RIGHT TO LOWER CENTRE\n  '\\u{1FBBF}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.5,0 L1,.5 L.5,1 L0,.5 Z', type: CustomGlyphVectorType.STROKE } },           // NEGATIVE DIAGONAL DIAMOND\n\n  // Terminal graphic characters (1FBC0-1FBCA)\n  '\\u{1FBC0}': { type: CustomGlyphDefinitionType.PATH, data: 'M.16,.445 A.02,.01,0,0,1,.39,.33 L.5,.375 L.61,.33 A.02,.01,0,0,1,.84,.445 L.75,.5 L.84,.555 A.02,.01,0,0,1,.61,.67 L.5,.625 L.39,.67 A.02,.01,0,0,1,.16,.555 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z', scaleType: CustomGlyphScaleType.CHAR }, // WHITE HEAVY SALTIRE WITH ROUNDED CORNERS\n  // 1FBC1-1FBC4 is dervied from the Iosevka font (SIL OFL v1.1) https://github.com/be5invis/Iosevka\n  '\\u{1FBC1}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z' }, // LEFT THIRD WHITE RIGHT POINTING INDEX\n  '\\u{1FBC2}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z' }, // MIDDLE THIRD WHITE RIGHT POINTING INDEX\n  '\\u{1FBC3}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z' }, // RIGHT THIRD WHITE RIGHT POINTING INDEX\n  '\\u{1FBC4}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z', scaleType: CustomGlyphScaleType.CHAR }, // NEGATIVE SQUARED QUESTION MARK\n  '\\u{1FBC5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.425 L.44,.51 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.51 L.56,.425 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // STICK FIGURE\n  '\\u{1FBC6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.425 L.44,.46 L.23,.385 L.19,.43 L.44,.51 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.61,.735 L.7,.71 L.56,.56 L.56,.51 L.81,.43 L.77,.385 L.56,.46 L.56,.425 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // STICK FIGURE WITH ARMS RAISED\n  '\\u{1FBC7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.425 L.44,.56 L.29,.71 L.38,.735 L.5,.605 L.74,.72 L.81,.685 L.56,.56 L.56,.425 Z M.18,.53 L.23,.575 L.81,.43 L.77,.385 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // STICK FIGURE LEANING LEFT\n  '\\u{1FBC8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.425 L.44,.56 L.19,.685 L.26,.72 L.5,.605 L.62,.735 L.71,.71 L.56,.56 L.56,.425 Z M.23,.385 L.18,.43 L.77,.575 L.81,.53 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // STICK FIGURE LEANING RIGHT\n  '\\u{1FBC9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.425 L.45,.495 L.15,.645 L.34,.645 L.34,.7 L.44,.7 L.44,.645 L.56,.645 L.56,.7 L.66,.7 L.66,.645 L.84,.645 L.54,.495 L.56,.425 Z M.39,.6 L.5,.55 L.60,.6 Z M.17,.46 L.17,.51 L.83,.51 L.83,.46 Z M.67,.35 C.67,.303,.594,.265,.5,.265 C.406,.265,.33,.303,.33,.35 C.33,.397,.406,.435,.5,.435 C.594,.435,.67,.397,.67,.35 Z M.56,.35 C.56,.3665,.533,.38,.5,.38 C.467,.38,.44,.3665,.44,.35 C.44,.3335,.467,.32,.5,.32 C.533,.32,.56,.3335,.56,.35 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // STICK FIGURE WITH DRESS\n  '\\u{1FBCA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.26,.375 L.5,.255 L.74,.375 L.74,.665 L.5,.55 L.26,.665 Z M.37,.395 L.37,.54 L.5,.475 L.63,.54 L.63,.395 L.5,.33 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // WHITE UP-POINTING CHEVRON\n\n  // Terminal graphic characters (1FBCB-1FBCD)\n  '\\u{1FBCB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.09,.41 L.32,.295 L.5,.375 L.68,.295 L.91,.41 L.75,.5 L.91,.59 L.68,.705 L.5,.625 L.32,.705 L.09,.59 L.25,.5 Z M.24,.41 L.39,.5 L.24,.59 L.32,.63 L.5,.555 L.68,.63 L.76,.59 L.61,.5 L.76,.41 L.68,.37 L.5,.445 L.32,.37 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // WHITE CROSS MARK\n  '\\u{1FBCC}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.55,.305 L.88,.305 L.88,.355 L.65,.355 L.65,.47 L.88,.47 L.88,.52 L.55,.52 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // RAISED SMALL LEFT SQUARE BRACKET\n  '\\u{1FBCD}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.38,.39 L.5,.33 L.62,.39 L.62,.53 L.5,.47 L.38,.53 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // BLACK SMALL UP-POINTING CHEVRON\n\n  // Block elements (1FBCE-1FBCF)\n  '\\u{1FBCE}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.6667,0 L0.6667,1 L0,1 Z' }, // LEFT TWO THIRDS BLOCK\n  '\\u{1FBCF}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.3333,0 L0.3333,1 L0,1 Z' }, // LEFT ONE THIRD BLOCK\n\n  // Character cell diagonals (1FBD0-1FBDF)\n  '\\u{1FBD0}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,.5 L0,1', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO LOWER LEFT\n  '\\u{1FBD1}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,0 L0,.5', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO MIDDLE LEFT\n  '\\u{1FBD2}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L1,.5', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE RIGHT\n  '\\u{1FBD3}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L1,1', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER RIGHT\n  '\\u{1FBD4}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L.5,1', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER CENTRE\n  '\\u{1FBD5}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L1,1', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO LOWER RIGHT\n  '\\u{1FBD6}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,0 L.5,1', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER CENTRE\n  '\\u{1FBD7}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L0,1', strokeWidth: 1 },       // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO LOWER LEFT\n  '\\u{1FBD8}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L.5,.5 L1,0', strokeWidth: 1 }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE CENTRE TO UPPER RIGHT\n  '\\u{1FBD9}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,0 L.5,.5 L1,1', strokeWidth: 1 }, // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO MIDDLE CENTRE TO LOWER RIGHT\n  '\\u{1FBDA}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,1 L.5,.5 L1,1', strokeWidth: 1 }, // BOX DRAWINGS LIGHT DIAGONAL LOWER LEFT TO MIDDLE CENTRE TO LOWER RIGHT\n  '\\u{1FBDB}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L.5,.5 L0,1', strokeWidth: 1 }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE CENTRE TO LOWER LEFT\n  '\\u{1FBDC}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L.5,1 L1,0', strokeWidth: 1 },  // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER CENTRE TO UPPER RIGHT\n  '\\u{1FBDD}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M1,0 L0,.5 L1,1', strokeWidth: 1 },  // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO MIDDLE LEFT TO LOWER RIGHT\n  '\\u{1FBDE}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,1 L.5,0 L1,1', strokeWidth: 1 },  // BOX DRAWINGS LIGHT DIAGONAL LOWER LEFT TO UPPER CENTRE TO LOWER RIGHT\n  '\\u{1FBDF}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L1,.5 L0,1', strokeWidth: 1 },  // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE RIGHT TO LOWER LEFT\n\n  // Geometric shapes (1FBE0-1FBEF)\n  '\\u{1FBE0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0', type: CustomGlyphVectorType.STROKE } }, // TOP JUSTIFIED LOWER HALF WHITE CIRCLE\n  '\\u{1FBE1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1', type: CustomGlyphVectorType.STROKE } }, // RIGHT JUSTIFIED LEFT HALF WHITE CIRCLE\n  '\\u{1FBE2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1', type: CustomGlyphVectorType.STROKE } }, // BOTTOM JUSTIFIED UPPER HALF WHITE CIRCLE\n  '\\u{1FBE3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1', type: CustomGlyphVectorType.STROKE } }, // LEFT JUSTIFIED RIGHT HALF WHITE CIRCLE\n  '\\u{1FBE4}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 0, w: 4, h: 4 }] },                                                   // UPPER CENTRE ONE QUARTER BLOCK\n  '\\u{1FBE5}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 4, w: 4, h: 4 }] },                                                   // LOWER CENTRE ONE QUARTER BLOCK\n  '\\u{1FBE6}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 2, w: 4, h: 4 }] },                                                   // MIDDLE LEFT ONE QUARTER BLOCK\n  '\\u{1FBE7}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 2, w: 4, h: 4 }] },                                                   // MIDDLE RIGHT ONE QUARTER BLOCK\n  '\\u{1FBE8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z', type: CustomGlyphVectorType.FILL } }, // TOP JUSTIFIED LOWER HALF BLACK CIRCLE\n  '\\u{1FBE9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z', type: CustomGlyphVectorType.FILL } }, // RIGHT JUSTIFIED LEFT HALF BLACK CIRCLE\n  '\\u{1FBEA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z', type: CustomGlyphVectorType.FILL } }, // BOTTOM JUSTIFIED UPPER HALF BLACK CIRCLE\n  '\\u{1FBEB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z', type: CustomGlyphVectorType.FILL } }, // LEFT JUSTIFIED RIGHT HALF BLACK CIRCLE\n  '\\u{1FBEC}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z', type: CustomGlyphVectorType.FILL } },               // TOP RIGHT JUSTIFIED LOWER LEFT QUARTER BLACK CIRCLE\n  '\\u{1FBED}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z', type: CustomGlyphVectorType.FILL } },               // BOTTOM LEFT JUSTIFIED UPPER RIGHT QUARTER BLACK CIRCLE\n  '\\u{1FBEE}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z', type: CustomGlyphVectorType.FILL } },               // BOTTOM RIGHT JUSTIFIED UPPER LEFT QUARTER BLACK CIRCLE\n  '\\u{1FBEF}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z', type: CustomGlyphVectorType.FILL } },               // TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE\n\n  // Segmented digits (1FBF0-1FBF9)\n  '\\u{1FBF0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111110), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT ZERO (abcdef)\n  '\\u{1FBF1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b0110000), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT ONE (bc)\n  '\\u{1FBF2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1101101), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT TWO (abdeg)\n  '\\u{1FBF3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111001), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT THREE (abcdg)\n  '\\u{1FBF4}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b0110011), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT FOUR (bcfg)\n  '\\u{1FBF5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1011011), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT FIVE (acdfg)\n  '\\u{1FBF6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1011111), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT SIX (acdefg)\n  '\\u{1FBF7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1110010), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT SEVEN (abcf)\n  '\\u{1FBF8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111111), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT EIGHT (abcdefg)\n  '\\u{1FBF9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111011), type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SEGMENTED DIGIT NINE (abcdfg)\n\n  // Terminal graphic character (1FBFA-1FBFA)\n  '\\u{1FBFA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // ALARM BELL SYMBOL\n\n  // #endregion\n\n  // #region Braille Patterns (2800-28FF)\n\n  // https://www.unicode.org/charts/PDF/U2800.pdf\n\n  // Braille patterns (2800-28FF)\n  ...Object.fromEntries(\n    Array.from({ length: 256 }, (_, i) => [\n      String.fromCodePoint(0x2800 + i),\n      { type: CustomGlyphDefinitionType.BRAILLE, data: i }\n    ])\n  ),\n\n  // #endregion\n};\n\nexport const blockPatternCodepoints = new Set<number>([\n  // Shade characters (2591-2593)\n  0x2591,\n  0x2592,\n  0x2593,\n  // Rectangular shade characters (1FB8C-1FB94)\n  0x1FB8C,\n  0x1FB8D,\n  0x1FB8E,\n  0x1FB8F,\n  0x1FB90,\n  0x1FB91,\n  0x1FB92,\n  0x1FB94,\n  // Triangular shade characters (1FB9C-1FB9F)\n  0x1FB9C,\n  0x1FB9D,\n  0x1FB9E,\n  0x1FB9F\n]);\n\n/**\n * Generates a drawing function for sextant characters. Sextants are a 2x3 grid where each cell\n * can be on or off.\n * @param pattern A 6-bit pattern where bit 0 = top-left, bit 1 = top-right, bit 2 = middle-left,\n * bit 3 = middle-right, bit 4 = bottom-left, bit 5 = bottom-right\n */\nfunction sextant(pattern: number): { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition } {\n  return {\n    type: CustomGlyphDefinitionType.PATH_FUNCTION,\n    data: () => {\n      // Sextant grid: 2 columns, 3 rows\n      // Row heights in 8ths: top=3, middle=2, bottom=3\n      // Column widths: left=4, right=4\n      const rects: string[] = [];\n      const colW = 0.5; // Each column is half width\n      const rowH = [3 / 8, 2 / 8, 3 / 8]; // Row heights as fractions\n      const rowY = [0, 3 / 8, 5 / 8]; // Row Y positions\n\n      for (let row = 0; row < 3; row++) {\n        const leftBit = (pattern >> (row * 2)) & 1;\n        const rightBit = (pattern >> (row * 2 + 1)) & 1;\n\n        if (leftBit && rightBit) {\n          // Full row\n          rects.push(`M0,${rowY[row]} L1,${rowY[row]} L1,${rowY[row] + rowH[row]} L0,${rowY[row] + rowH[row]} Z`);\n        } else if (leftBit) {\n          rects.push(`M0,${rowY[row]} L${colW},${rowY[row]} L${colW},${rowY[row] + rowH[row]} L0,${rowY[row] + rowH[row]} Z`);\n        } else if (rightBit) {\n          rects.push(`M${colW},${rowY[row]} L1,${rowY[row]} L1,${rowY[row] + rowH[row]} L${colW},${rowY[row] + rowH[row]} Z`);\n        }\n      }\n      return rects.join(' ');\n    }\n  };\n}\n\n/**\n * Generates SVG path data for a 7-segment display digit.\n *\n * Segment mapping (bit positions):\n *\n * - bit 6: a (top)\n * - bit 5: b (upper right)\n * - bit 4: c (lower right)\n * - bit 3: d (bottom)\n * - bit 2: e (lower left)\n * - bit 1: f (upper left)\n * - bit 0: g (middle)\n *\n * ```\n *   ─a─\n *  │   │\n *  f   b\n *   ─g─\n *  e   c\n *  │   │\n *   ─d─\n * ```\n */\nfunction segmentedDigit(pattern: number): string {\n  const paths: string[] = [];\n\n  // Each segment should have approximately the same stroke width, this is somewhat difficult to be\n  // precise since coordinates are 0-1 of the whole cell (percentage-based). To handle this, the\n  // fact that terminal cells are typically sized at ~2:1 (height:width) is leveraged.\n  // for horizontal vs vertical to make segments appear the same thickness\n  const segW = 0.15;  // Width of vertical segments (fraction of cell width)\n  const segH = 0.075; // Height of horizontal segments (fraction of cell height, ~half of segW for 2:1 cells)\n  const padX = 0.05;  // Horizontal padding from edge\n  const padY = 0.175; // Vertical padding from edge (35% total = 65% height)\n  const gap = 0.015;  // Gap between segments\n  const taperX = segW / 2; // Horizontal taper for vertical segments\n  const taperY = segH / 2; // Vertical taper for horizontal segments\n\n  const left = padX;\n  const right = 1 - padX;\n  const top = padY;\n  const bottom = 1 - padY;\n  const midY = 0.5;\n\n  // Segment a (top horizontal) - hexagonal with pointed left/right ends\n  if (pattern & 0b1000000) {\n    const y1 = top;\n    const y2 = top + segH / 2;\n    const y3 = top + segH;\n    const x1 = left + segW + gap;\n    const x2 = right - segW - gap;\n    paths.push(`M${x1},${y2} L${x1 + taperX},${y1} L${x2 - taperX},${y1} L${x2},${y2} L${x2 - taperX},${y3} L${x1 + taperX},${y3} Z`);\n  }\n  // Segment b (upper right vertical) - hexagonal with pointed top/bottom ends\n  if (pattern & 0b0100000) {\n    const x1 = right - segW;\n    const x2 = right - segW / 2;\n    const x3 = right;\n    const y1 = top + segH + gap;\n    const y2 = midY - gap;\n    paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);\n  }\n  // Segment c (lower right vertical) - hexagonal with pointed top/bottom ends\n  if (pattern & 0b0010000) {\n    const x1 = right - segW;\n    const x2 = right - segW / 2;\n    const x3 = right;\n    const y1 = midY + gap;\n    const y2 = bottom - segH - gap;\n    paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);\n  }\n  // Segment d (bottom horizontal) - hexagonal with pointed left/right ends\n  if (pattern & 0b0001000) {\n    const y1 = bottom - segH;\n    const y2 = bottom - segH / 2;\n    const y3 = bottom;\n    const x1 = left + segW + gap;\n    const x2 = right - segW - gap;\n    paths.push(`M${x1},${y2} L${x1 + taperX},${y1} L${x2 - taperX},${y1} L${x2},${y2} L${x2 - taperX},${y3} L${x1 + taperX},${y3} Z`);\n  }\n  // Segment e (lower left vertical) - hexagonal with pointed top/bottom ends\n  if (pattern & 0b0000100) {\n    const x1 = left;\n    const x2 = left + segW / 2;\n    const x3 = left + segW;\n    const y1 = midY + gap;\n    const y2 = bottom - segH - gap;\n    paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);\n  }\n  // Segment f (upper left vertical) - hexagonal with pointed top/bottom ends\n  if (pattern & 0b0000010) {\n    const x1 = left;\n    const x2 = left + segW / 2;\n    const x3 = left + segW;\n    const y1 = top + segH + gap;\n    const y2 = midY - gap;\n    paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);\n  }\n  // Segment g (middle horizontal) - hexagonal with pointed left/right ends\n  if (pattern & 0b0000001) {\n    const y1 = midY - segH / 2;\n    const y2 = midY;\n    const y3 = midY + segH / 2;\n    const x1 = left + segW + gap;\n    const x2 = right - segW - gap;\n    paths.push(`M${x1},${y2} L${x1 + taperX},${y1} L${x2 - taperX},${y1} L${x2},${y2} L${x2 - taperX},${y3} L${x1 + taperX},${y3} Z`);\n  }\n\n  return paths.join(' ');\n}\n\n"
  },
  {
    "path": "addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\nimport { customGlyphDefinitions } from './CustomGlyphDefinitions';\nimport { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';\n\n/**\n * Try drawing a custom block element or box drawing character, returning whether it was\n * successfully drawn.\n */\nexport function tryDrawCustomGlyph(\n  ctx: CanvasRenderingContext2D,\n  c: string,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  deviceCharWidth: number,\n  deviceCharHeight: number,\n  fontSize: number,\n  devicePixelRatio: number,\n  backgroundColor?: string,\n  variantOffset: number = 0\n): boolean {\n  const unifiedCharDefinition = customGlyphDefinitions[c];\n  if (unifiedCharDefinition) {\n    // Normalize to array for uniform handling\n    const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];\n    for (const part of parts) {\n      drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor, variantOffset);\n    }\n    return true;\n  }\n\n  return false;\n}\n\nfunction drawDefinitionPart(\n  ctx: CanvasRenderingContext2D,\n  part: CustomGlyphDefinitionPart,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  deviceCharWidth: number,\n  deviceCharHeight: number,\n  fontSize: number,\n  devicePixelRatio: number,\n  backgroundColor?: string,\n  variantOffset: number = 0\n): void {\n  // Handle scaleType - adjust dimensions and offset when scaling to character area\n  let drawWidth = deviceCellWidth;\n  let drawHeight = deviceCellHeight;\n  let drawXOffset = xOffset;\n  let drawYOffset = yOffset;\n  if (part.scaleType === CustomGlyphScaleType.CHAR) {\n    drawWidth = deviceCharWidth;\n    drawHeight = deviceCharHeight;\n    // Center the character within the cell\n    drawXOffset = xOffset + (deviceCellWidth - deviceCharWidth) / 2;\n    drawYOffset = yOffset + (deviceCellHeight - deviceCharHeight) / 2;\n  }\n\n  // Handle clipPath generically for any definition type\n  if (part.clipPath) {\n    ctx.save();\n    applyClipPath(ctx, part.clipPath, drawXOffset, drawYOffset, drawWidth, drawHeight);\n  }\n\n  switch (part.type) {\n    case CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR:\n      drawBlockVectorChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);\n      break;\n    case CustomGlyphDefinitionType.BLOCK_PATTERN:\n      drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);\n      break;\n    case CustomGlyphDefinitionType.PATH_FUNCTION:\n      drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);\n      break;\n    case CustomGlyphDefinitionType.PATH:\n      drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);\n      break;\n    case CustomGlyphDefinitionType.PATH_NEGATIVE:\n      drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);\n      break;\n    case CustomGlyphDefinitionType.VECTOR_SHAPE:\n      drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio);\n      break;\n    case CustomGlyphDefinitionType.BRAILLE:\n      drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);\n      break;\n  }\n\n  if (part.clipPath) {\n    ctx.restore();\n  }\n}\n\nfunction drawBlockVectorChar(\n  ctx: CanvasRenderingContext2D,\n  charDefinition: ICustomGlyphSolidOctantBlockVector[],\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number\n): void {\n  for (let i = 0; i < charDefinition.length; i++) {\n    const box = charDefinition[i];\n    const xEighth = deviceCellWidth / 8;\n    const yEighth = deviceCellHeight / 8;\n    ctx.fillRect(\n      xOffset + box.x * xEighth,\n      yOffset + box.y * yEighth,\n      box.w * xEighth,\n      box.h * yEighth\n    );\n  }\n}\n\n/**\n * Braille dot positions in octant coordinates (x, y for center of each dot area)\n * Columns: left=1-2, right=5-6 (leaving 0 and 7 as margins, 3-4 as gap)\n * Rows: 0-1, 2-3, 4-5, 6-7 for the 4 rows\n */\nconst brailleDotPositions = new Uint8Array([\n  1, 0, // dot 1 - bit 0\n  1, 2, // dot 2 - bit 1\n  1, 4, // dot 3 - bit 2\n  5, 0, // dot 4 - bit 3\n  5, 2, // dot 5 - bit 4\n  5, 4, // dot 6 - bit 5\n  1, 6, // dot 7 - bit 6\n  5, 6, // dot 8 - bit 7\n]);\n\n/**\n * Draws a braille pattern\n */\nfunction drawBrailleCharacter(\n  ctx: CanvasRenderingContext2D,\n  pattern: number,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number\n): void {\n  const xEighth = deviceCellWidth / 8;\n  const paddingY = deviceCellHeight * 0.1;\n  const usableHeight = deviceCellHeight * 0.8;\n  const yEighth = usableHeight / 8;\n  const radius = Math.min(xEighth, yEighth);\n\n  for (let bit = 0; bit < 8; bit++) {\n    if (pattern & (1 << bit)) {\n      const x = brailleDotPositions[bit * 2];\n      const y = brailleDotPositions[bit * 2 + 1];\n      const cx = xOffset + (x + 1) * xEighth;\n      const cy = yOffset + paddingY + (y + 1) * yEighth;\n      ctx.beginPath();\n      ctx.arc(cx, cy, radius, 0, Math.PI * 2);\n      ctx.fill();\n    }\n  }\n}\n\nfunction drawPathDefinitionCharacter(\n  ctx: CanvasRenderingContext2D,\n  charDefinition: CustomGlyphPathDrawFunctionDefinition | string,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  devicePixelRatio: number,\n  strokeWidth?: number\n): void {\n  const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);\n  ctx.beginPath();\n  let currentX = 0;\n  let currentY = 0;\n  let lastControlX = 0;\n  let lastControlY = 0;\n  let lastCommand = '';\n  for (const instruction of instructions.split(' ')) {\n    const type = instruction[0];\n    const args: string[] = instruction.substring(1).split(',');\n    if (type === 'Z') {\n      ctx.closePath();\n      lastCommand = type;\n      continue;\n    }\n    if (type === 'V') {\n      const y = yOffset + parseFloat(args[0]) * deviceCellHeight;\n      ctx.lineTo(currentX, y);\n      currentY = y;\n      lastControlX = currentX;\n      lastControlY = currentY;\n      lastCommand = type;\n      continue;\n    }\n    if (type === 'H') {\n      const x = xOffset + parseFloat(args[0]) * deviceCellWidth;\n      ctx.lineTo(x, currentY);\n      currentX = x;\n      lastControlX = currentX;\n      lastControlY = currentY;\n      lastCommand = type;\n      continue;\n    }\n    if (!args[0] || !args[1]) {\n      continue;\n    }\n    if (type === 'A') {\n      // SVG arc: A rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y\n      const rx = parseFloat(args[0]) * deviceCellWidth;\n      const ry = parseFloat(args[1]) * deviceCellHeight;\n      const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;\n      const largeArcFlag = parseInt(args[3]);\n      const sweepFlag = parseInt(args[4]);\n      const x = xOffset + parseFloat(args[5]) * deviceCellWidth;\n      const y = yOffset + parseFloat(args[6]) * deviceCellHeight;\n      drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);\n      currentX = x;\n      currentY = y;\n      continue;\n    }\n    const translatedArgs = args.map((e, i) => {\n      const val = parseFloat(e);\n      return i % 2 === 0\n        ? xOffset + val * deviceCellWidth\n        : yOffset + val * deviceCellHeight;\n    });\n    if (type === 'M') {\n      ctx.moveTo(translatedArgs[0], translatedArgs[1]);\n      currentX = translatedArgs[0];\n      currentY = translatedArgs[1];\n      lastControlX = currentX;\n      lastControlY = currentY;\n    } else if (type === 'L') {\n      ctx.lineTo(translatedArgs[0], translatedArgs[1]);\n      currentX = translatedArgs[0];\n      currentY = translatedArgs[1];\n      lastControlX = currentX;\n      lastControlY = currentY;\n    } else if (type === 'Q') {\n      ctx.quadraticCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3]);\n      lastControlX = translatedArgs[0];\n      lastControlY = translatedArgs[1];\n      currentX = translatedArgs[2];\n      currentY = translatedArgs[3];\n    } else if (type === 'T') {\n      // T uses reflection of last control point if previous command was Q or T\n      let cpX: number;\n      let cpY: number;\n      if (lastCommand === 'Q' || lastCommand === 'T') {\n        cpX = 2 * currentX - lastControlX;\n        cpY = 2 * currentY - lastControlY;\n      } else {\n        cpX = currentX;\n        cpY = currentY;\n      }\n      ctx.quadraticCurveTo(cpX, cpY, translatedArgs[0], translatedArgs[1]);\n      lastControlX = cpX;\n      lastControlY = cpY;\n      currentX = translatedArgs[0];\n      currentY = translatedArgs[1];\n    } else if (type === 'C') {\n      ctx.bezierCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3], translatedArgs[4], translatedArgs[5]);\n      lastControlX = translatedArgs[2];\n      lastControlY = translatedArgs[3];\n      currentX = translatedArgs[4];\n      currentY = translatedArgs[5];\n    }\n    lastCommand = type;\n  }\n  if (strokeWidth !== undefined) {\n    ctx.strokeStyle = ctx.fillStyle;\n    ctx.lineWidth = devicePixelRatio * strokeWidth;\n    ctx.stroke();\n  } else {\n    ctx.fill();\n  }\n}\n\n/**\n * Converts SVG arc parameters to canvas arc/ellipse calls.\n * Based on the SVG spec's endpoint to center parameterization conversion.\n */\nfunction drawSvgArc(\n  ctx: CanvasRenderingContext2D,\n  x1: number, y1: number,\n  rx: number, ry: number,\n  phi: number,\n  largeArcFlag: number,\n  sweepFlag: number,\n  x2: number, y2: number\n): void {\n  // Handle degenerate cases\n  if (rx === 0 || ry === 0) {\n    ctx.lineTo(x2, y2);\n    return;\n  }\n\n  rx = Math.abs(rx);\n  ry = Math.abs(ry);\n\n  const cosPhi = Math.cos(phi);\n  const sinPhi = Math.sin(phi);\n\n  // Step 1: Compute (x1', y1')\n  const dx = (x1 - x2) / 2;\n  const dy = (y1 - y2) / 2;\n  const x1p = cosPhi * dx + sinPhi * dy;\n  const y1p = -sinPhi * dx + cosPhi * dy;\n\n  // Step 2: Compute (cx', cy')\n  let rxSq = rx * rx;\n  let rySq = ry * ry;\n  const x1pSq = x1p * x1p;\n  const y1pSq = y1p * y1p;\n\n  // Correct radii if necessary\n  const lambda = x1pSq / rxSq + y1pSq / rySq;\n  if (lambda > 1) {\n    const lambdaSqrt = Math.sqrt(lambda);\n    rx *= lambdaSqrt;\n    ry *= lambdaSqrt;\n    rxSq = rx * rx;\n    rySq = ry * ry;\n  }\n\n  let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);\n  if (sq < 0) sq = 0;\n  const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);\n  const cxp = coef * (rx * y1p / ry);\n  const cyp = coef * -(ry * x1p / rx);\n\n  // Step 3: Compute (cx, cy) from (cx', cy')\n  const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;\n  const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;\n\n  // Step 4: Compute angles\n  const ux = (x1p - cxp) / rx;\n  const uy = (y1p - cyp) / ry;\n  const vx = (-x1p - cxp) / rx;\n  const vy = (-y1p - cyp) / ry;\n\n  const startAngle = Math.atan2(uy, ux);\n  let dTheta = Math.atan2(vy, vx) - startAngle;\n\n  if (sweepFlag === 0 && dTheta > 0) {\n    dTheta -= 2 * Math.PI;\n  } else if (sweepFlag === 1 && dTheta < 0) {\n    dTheta += 2 * Math.PI;\n  }\n\n  const endAngle = startAngle + dTheta;\n\n  ctx.ellipse(cx, cy, rx, ry, phi, startAngle, endAngle, sweepFlag === 0);\n}\n\n/**\n * Draws a \"negative\" path where the background color is used to draw the shape on top of a\n * foreground-filled cell. This creates the appearance of a cutout without using actual\n * transparency, which allows SPAA (subpixel anti-aliasing) to work correctly.\n *\n * @param ctx The canvas rendering context (fillStyle should be set to foreground color)\n * @param charDefinition The vector shape definition for the negative shape\n * @param xOffset The x offset to draw at\n * @param yOffset The y offset to draw at\n * @param deviceCellWidth The width of the cell in device pixels\n * @param deviceCellHeight The height of the cell in device pixels\n * @param devicePixelRatio The device pixel ratio\n * @param backgroundColor The background color to use for the \"cutout\" portion\n */\nfunction drawPathNegativeDefinitionCharacter(\n  ctx: CanvasRenderingContext2D,\n  charDefinition: ICustomGlyphVectorShape,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  devicePixelRatio: number,\n  backgroundColor?: string\n): void {\n  ctx.save();\n\n  // First, fill the entire cell with foreground color\n  ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);\n\n  // Then draw the \"negative\" shape with the background color\n  if (backgroundColor) {\n    ctx.fillStyle = backgroundColor;\n    ctx.strokeStyle = backgroundColor;\n  }\n\n  ctx.lineWidth = devicePixelRatio;\n  ctx.lineCap = 'square';\n  ctx.beginPath();\n  for (const instruction of charDefinition.d.split(' ')) {\n    const type = instruction[0];\n    const args: string[] = instruction.substring(1).split(',');\n    if (!args[0] || !args[1]) {\n      if (type === 'Z') {\n        ctx.closePath();\n      }\n      continue;\n    }\n    const translatedArgs = args.map((e, i) => {\n      const val = parseFloat(e);\n      return i % 2 === 0\n        ? xOffset + val * deviceCellWidth\n        : yOffset + val * deviceCellHeight;\n    });\n    if (type === 'M') {\n      ctx.moveTo(translatedArgs[0], translatedArgs[1]);\n    } else if (type === 'L') {\n      ctx.lineTo(translatedArgs[0], translatedArgs[1]);\n    }\n  }\n\n  if (charDefinition.type === CustomGlyphVectorType.STROKE) {\n    ctx.stroke();\n  } else {\n    ctx.fill();\n  }\n\n  ctx.restore();\n}\n\nconst cachedPatterns: Map<CustomGlyphPatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();\n\nfunction drawPatternChar(\n  ctx: CanvasRenderingContext2D,\n  charDefinition: number[][],\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  variantOffset: number = 0\n): void {\n  let patternSet = cachedPatterns.get(charDefinition);\n  if (!patternSet) {\n    patternSet = new Map();\n    cachedPatterns.set(charDefinition, patternSet);\n  }\n  const fillStyle = ctx.fillStyle;\n  if (typeof fillStyle !== 'string') {\n    throw new Error(`Unexpected fillStyle type \"${fillStyle}\"`);\n  }\n  let pattern = patternSet.get(fillStyle);\n  if (!pattern) {\n    const width = charDefinition[0].length;\n    const height = charDefinition.length;\n    const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas');\n    tmpCanvas.width = width;\n    tmpCanvas.height = height;\n    const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));\n    const imageData = new ImageData(width, height);\n\n    // Extract rgba from fillStyle\n    let r: number;\n    let g: number;\n    let b: number;\n    let a: number;\n    if (fillStyle.startsWith('#')) {\n      r = parseInt(fillStyle.slice(1, 3), 16);\n      g = parseInt(fillStyle.slice(3, 5), 16);\n      b = parseInt(fillStyle.slice(5, 7), 16);\n      a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1;\n    } else if (fillStyle.startsWith('rgba')) {\n      ([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));\n    } else {\n      throw new Error(`Unexpected fillStyle color format \"${fillStyle}\" when drawing pattern glyph`);\n    }\n\n    for (let y = 0; y < height; y++) {\n      for (let x = 0; x < width; x++) {\n        imageData.data[(y * width + x) * 4    ] = r;\n        imageData.data[(y * width + x) * 4 + 1] = g;\n        imageData.data[(y * width + x) * 4 + 2] = b;\n        imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255);\n      }\n    }\n    tmpCtx.putImageData(imageData, 0, 0);\n    pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));\n    patternSet.set(fillStyle, pattern);\n  }\n  // Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.\n  // variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.\n  const dx = (variantOffset >> 1) & 1;\n  const dy = variantOffset & 1;\n  if (dx !== 0 || dy !== 0) {\n    pattern.setTransform(new DOMMatrix().translateSelf(-dx, -dy));\n  } else {\n    pattern.setTransform(new DOMMatrix());\n  }\n  ctx.fillStyle = pattern;\n  ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);\n}\n\nfunction drawPathFunctionCharacter(\n  ctx: CanvasRenderingContext2D,\n  charDefinition: string | ((xp: number, yp: number) => string),\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  devicePixelRatio: number,\n  strokeWidth?: number\n): void {\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);\n  ctx.clip();\n\n  ctx.beginPath();\n  let actualInstructions: string;\n  if (typeof charDefinition === 'function') {\n    const xp = .15;\n    const yp = .15 / deviceCellHeight * deviceCellWidth;\n    actualInstructions = charDefinition(xp, yp);\n  } else {\n    actualInstructions = charDefinition;\n  }\n  const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };\n  for (const instruction of actualInstructions.split(' ')) {\n    const type = instruction[0];\n    if (type === 'Z') {\n      ctx.closePath();\n      state.lastCommand = type;\n      continue;\n    }\n    const f = svgToCanvasInstructionMap[type];\n    if (!f) {\n      console.error(`Could not find drawing instructions for \"${type}\"`);\n      continue;\n    }\n    const args: string[] = instruction.substring(1).split(',');\n    if (!args[0] || !args[1]) {\n      continue;\n    }\n    f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio, 0, 0, false), state);\n    state.lastCommand = type;\n  }\n  if (strokeWidth !== undefined) {\n    ctx.strokeStyle = ctx.fillStyle;\n    ctx.lineWidth = devicePixelRatio * strokeWidth;\n    ctx.stroke();\n  } else {\n    ctx.fill();\n  }\n  ctx.closePath();\n  ctx.restore();\n}\n\n/**\n * Applies a clip path to the canvas context from SVG-like path instructions.\n */\nfunction applyClipPath(\n  ctx: CanvasRenderingContext2D,\n  clipPath: string,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number\n): void {\n  ctx.beginPath();\n  for (const instruction of clipPath.split(' ')) {\n    const type = instruction[0];\n    if (type === 'Z') {\n      ctx.closePath();\n      continue;\n    }\n    const args: string[] = instruction.substring(1).split(',');\n    if (!args[0] || !args[1]) {\n      continue;\n    }\n    const x = xOffset + parseFloat(args[0]) * deviceCellWidth;\n    const y = yOffset + parseFloat(args[1]) * deviceCellHeight;\n    if (type === 'M') {\n      ctx.moveTo(x, y);\n    } else if (type === 'L') {\n      ctx.lineTo(x, y);\n    }\n  }\n  ctx.clip();\n}\n\nfunction drawVectorShape(\n  ctx: CanvasRenderingContext2D,\n  charDefinition: ICustomGlyphVectorShape,\n  xOffset: number,\n  yOffset: number,\n  deviceCellWidth: number,\n  deviceCellHeight: number,\n  fontSize: number,\n  devicePixelRatio: number\n): void {\n  // Clip the cell to make sure drawing doesn't occur beyond bounds\n  const clipRegion = new Path2D();\n  clipRegion.rect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);\n  ctx.clip(clipRegion);\n\n  ctx.beginPath();\n  // Scale the stroke with DPR and font size\n  const cssLineWidth = fontSize / 12;\n  ctx.lineWidth = devicePixelRatio * cssLineWidth;\n  const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };\n  for (const instruction of charDefinition.d.split(' ')) {\n    const type = instruction[0];\n    if (type === 'Z') {\n      ctx.closePath();\n      state.lastCommand = type;\n      continue;\n    }\n    const f = svgToCanvasInstructionMap[type];\n    if (!f) {\n      console.error(`Could not find drawing instructions for \"${type}\"`);\n      continue;\n    }\n    const args: string[] = instruction.substring(1).split(',');\n    if (!args[0] || !args[1]) {\n      continue;\n    }\n    f(ctx, translateArgs(\n      args,\n      deviceCellWidth,\n      deviceCellHeight,\n      xOffset,\n      yOffset,\n      false,\n      devicePixelRatio,\n      (charDefinition.leftPadding ?? 0) * (cssLineWidth / 2),\n      (charDefinition.rightPadding ?? 0) * (cssLineWidth / 2)\n    ), state);\n    state.lastCommand = type;\n  }\n  if (charDefinition.type === CustomGlyphVectorType.STROKE) {\n    ctx.strokeStyle = ctx.fillStyle;\n    ctx.stroke();\n  } else {\n    ctx.fill();\n  }\n  ctx.closePath();\n}\n\nfunction clamp(value: number, max: number, min: number = 0): number {\n  return Math.max(Math.min(value, max), min);\n}\n\ninterface ISvgPathState {\n  currentX: number;\n  currentY: number;\n  lastControlX: number;\n  lastControlY: number;\n  lastCommand: string;\n}\n\nconst svgToCanvasInstructionMap: { [index: string]: (ctx: CanvasRenderingContext2D, args: number[], state: ISvgPathState) => void } = {\n  'C': (ctx, args, state) => {\n    ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]);\n    state.lastControlX = args[2];\n    state.lastControlY = args[3];\n    state.currentX = args[4];\n    state.currentY = args[5];\n  },\n  'L': (ctx, args, state) => {\n    ctx.lineTo(args[0], args[1]);\n    state.lastControlX = state.currentX = args[0];\n    state.lastControlY = state.currentY = args[1];\n  },\n  'M': (ctx, args, state) => {\n    ctx.moveTo(args[0], args[1]);\n    state.lastControlX = state.currentX = args[0];\n    state.lastControlY = state.currentY = args[1];\n  },\n  'Q': (ctx, args, state) => {\n    ctx.quadraticCurveTo(args[0], args[1], args[2], args[3]);\n    state.lastControlX = args[0];\n    state.lastControlY = args[1];\n    state.currentX = args[2];\n    state.currentY = args[3];\n  },\n  'T': (ctx, args, state) => {\n    let cpX: number;\n    let cpY: number;\n    if (state.lastCommand === 'Q' || state.lastCommand === 'T') {\n      cpX = 2 * state.currentX - state.lastControlX;\n      cpY = 2 * state.currentY - state.lastControlY;\n    } else {\n      cpX = state.currentX;\n      cpY = state.currentY;\n    }\n    ctx.quadraticCurveTo(cpX, cpY, args[0], args[1]);\n    state.lastControlX = cpX;\n    state.lastControlY = cpY;\n    state.currentX = args[0];\n    state.currentY = args[1];\n  }\n};\n\nfunction translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0, clampToCell: boolean = true): number[] {\n  const result = args.map(e => parseFloat(e) || parseInt(e));\n\n  if (result.length < 2) {\n    throw new Error('Too few arguments for instruction');\n  }\n\n  for (let x = 0; x < result.length; x += 2) {\n    // Translate from 0-1 to 0-cellWidth\n    result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio);\n    // Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally\n    // clamp to the cell bounds.\n    if (doClamp && result[x] !== 0) {\n      const rounded = Math.round(result[x] + 0.5) - 0.5;\n      result[x] = clampToCell ? clamp(rounded, cellWidth, 0) : rounded;\n    }\n    // Apply the cell's offset (ie. x*cellWidth)\n    result[x] += xOffset + (leftPadding * devicePixelRatio);\n  }\n\n  for (let y = 1; y < result.length; y += 2) {\n    // Translate from 0-1 to 0-cellHeight\n    result[y] *= cellHeight;\n    // Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally\n    // clamp to the cell bounds.\n    if (doClamp && result[y] !== 0) {\n      const rounded = Math.round(result[y] + 0.5) - 0.5;\n      result[y] = clampToCell ? clamp(rounded, cellHeight, 0) : rounded;\n    }\n    // Apply the cell's offset (ie. x*cellHeight)\n    result[y] += yOffset;\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/customGlyphs/Types.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport interface ICustomGlyphSolidOctantBlockVector {\n  x: number;\n  y: number;\n  w: number;\n  h: number;\n}\n\n/**\n * @param xp The percentage of 15% of the x axis.\n * @param yp The percentage of 15% of the x axis on the y axis.\n */\nexport type CustomGlyphPathDrawFunctionDefinition = (xp: number, yp: number) => string;\n\nexport interface ICustomGlyphVectorShape {\n  d: string;\n  type: CustomGlyphVectorType;\n  leftPadding?: number;\n  rightPadding?: number;\n}\n\nexport const enum CustomGlyphVectorType {\n  FILL,\n  STROKE\n}\n\nexport type CustomGlyphPatternDefinition = number[][];\n\nexport const enum CustomGlyphDefinitionType {\n  SOLID_OCTANT_BLOCK_VECTOR,\n  BLOCK_PATTERN,\n  PATH_FUNCTION,\n  PATH,\n  PATH_NEGATIVE,\n  VECTOR_SHAPE,\n  BRAILLE,\n}\n\nexport type CustomGlyphDefinitionPartRaw = (\n  { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: ICustomGlyphSolidOctantBlockVector[] } |\n  { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: CustomGlyphPatternDefinition } |\n  { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition | string } |\n  { type: CustomGlyphDefinitionType.PATH, data: string } |\n  { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |\n  { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape} |\n  { type: CustomGlyphDefinitionType.BRAILLE, data: number }\n);\n\nexport const enum CustomGlyphScaleType {\n  /**\n   * Scale to the entire cell, including letter spacing and line height.\n   */\n  CELL,\n  /**\n   * Scale to only the character area, excluding letter spacing and line height.\n   */\n  CHAR,\n}\n\nexport interface ICustomGlyphDefinitionCommon {\n  /**\n   * A custom clip path for the draw definition, restricting the area it can draw to.\n   */\n  clipPath?: string;\n  /**\n   * The stroke width to use when drawing the path. Defaults to 1.\n   */\n  strokeWidth?: number;\n  /**\n   * Defines how to scale the draw. Defaults to scaling to the full cell including letter spacing\n   * and line height.\n   */\n  scaleType?: CustomGlyphScaleType;\n}\n\nexport type CustomGlyphDefinitionPart = CustomGlyphDefinitionPartRaw & ICustomGlyphDefinitionCommon;\n\n/**\n * A character definition that can be a single part or an array of parts drawn in sequence.\n */\nexport type CustomGlyphCharacterDefinition = CustomGlyphDefinitionPart | CustomGlyphDefinitionPart[];\n"
  },
  {
    "path": "addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ReadonlyColorSet } from 'browser/Types';\nimport { acquireTextureAtlas } from '../CharAtlasCache';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport { ICoreBrowserService, IThemeService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { CellData } from 'common/buffer/CellData';\nimport { IOptionsService } from 'common/services/Services';\nimport { Terminal } from '@xterm/xterm';\nimport { IRenderLayer } from './Types';\nimport { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\nimport { TEXT_BASELINE } from '../Constants';\nimport type { ITextureAtlas } from '../Types';\n\nexport abstract class BaseRenderLayer extends Disposable implements IRenderLayer {\n  private _canvas: HTMLCanvasElement;\n  protected _ctx!: CanvasRenderingContext2D;\n  private _deviceCharWidth: number = 0;\n  private _deviceCharHeight: number = 0;\n  private _deviceCellWidth: number = 0;\n  private _deviceCellHeight: number = 0;\n  private _deviceCharLeft: number = 0;\n  private _deviceCharTop: number = 0;\n\n  protected _charAtlas: ITextureAtlas | undefined;\n\n  constructor(\n    terminal: Terminal,\n    private _container: HTMLElement,\n    id: string,\n    zIndex: number,\n    private _alpha: boolean,\n    protected readonly _coreBrowserService: ICoreBrowserService,\n    protected readonly _optionsService: IOptionsService,\n    protected readonly _themeService: IThemeService\n  ) {\n    super();\n    this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');\n    this._canvas.classList.add(`xterm-${id}-layer`);\n    this._canvas.style.zIndex = zIndex.toString();\n    this._initCanvas();\n    this._container.appendChild(this._canvas);\n    this._register(this._themeService.onChangeColors(e => {\n      this._refreshCharAtlas(terminal, e);\n      this.reset(terminal);\n    }));\n    this._register(toDisposable(() => {\n      this._canvas.remove();\n    }));\n  }\n\n  private _initCanvas(): void {\n    this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));\n    // Draw the background if this is an opaque layer\n    if (!this._alpha) {\n      this._clearAll();\n    }\n  }\n\n  public handleBlur(terminal: Terminal): void {}\n  public handleFocus(terminal: Terminal): void {}\n  public handleCursorMove(terminal: Terminal): void {}\n  public handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void {}\n  public handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}\n\n  protected _setTransparency(terminal: Terminal, alpha: boolean): void {\n    // Do nothing when alpha doesn't change\n    if (alpha === this._alpha) {\n      return;\n    }\n\n    // Create new canvas and replace old one\n    const oldCanvas = this._canvas;\n    this._alpha = alpha;\n    // Cloning preserves properties\n    this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;\n    this._initCanvas();\n    this._container.replaceChild(this._canvas, oldCanvas);\n\n    // Regenerate char atlas and force a full redraw\n    this._refreshCharAtlas(terminal, this._themeService.colors);\n    this.handleGridChanged(terminal, 0, terminal.rows - 1);\n  }\n\n  /**\n   * Refreshes the char atlas, aquiring a new one if necessary.\n   * @param terminal The terminal.\n   * @param colorSet The color set to use for the char atlas.\n   */\n  private _refreshCharAtlas(terminal: Terminal, colorSet: ReadonlyColorSet): void {\n    if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) {\n      return;\n    }\n\n    this._charAtlas = acquireTextureAtlas(terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr, 2048);\n    this._charAtlas.warmUp();\n  }\n\n  public resize(terminal: Terminal, dim: IRenderDimensions): void {\n    this._deviceCellWidth = dim.device.cell.width;\n    this._deviceCellHeight = dim.device.cell.height;\n    this._deviceCharWidth = dim.device.char.width;\n    this._deviceCharHeight = dim.device.char.height;\n    this._deviceCharLeft = dim.device.char.left;\n    this._deviceCharTop = dim.device.char.top;\n    this._canvas.width = dim.device.canvas.width;\n    this._canvas.height = dim.device.canvas.height;\n    this._canvas.style.width = `${dim.css.canvas.width}px`;\n    this._canvas.style.height = `${dim.css.canvas.height}px`;\n\n    // Draw the background if this is an opaque layer\n    if (!this._alpha) {\n      this._clearAll();\n    }\n\n    this._refreshCharAtlas(terminal, this._themeService.colors);\n  }\n\n  public abstract reset(terminal: Terminal): void;\n\n  /**\n   * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the\n   * existing fillStyle on the context.\n   * @param x The column to fill.\n   * @param y The row to fill.\n   */\n  protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {\n    this._ctx.fillRect(\n      x * this._deviceCellWidth,\n      (y + 1) * this._deviceCellHeight - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */,\n      width * this._deviceCellWidth,\n      this._coreBrowserService.dpr);\n  }\n\n  /**\n   * Clears the entire canvas.\n   */\n  protected _clearAll(): void {\n    if (this._alpha) {\n      this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);\n    } else {\n      this._ctx.fillStyle = this._themeService.colors.background.css;\n      this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);\n    }\n  }\n\n  /**\n   * Clears 1+ cells completely.\n   * @param x The column to start at.\n   * @param y The row to start at.\n   * @param width The number of columns to clear.\n   * @param height The number of rows to clear.\n   */\n  protected _clearCells(x: number, y: number, width: number, height: number): void {\n    if (this._alpha) {\n      this._ctx.clearRect(\n        x * this._deviceCellWidth,\n        y * this._deviceCellHeight,\n        width * this._deviceCellWidth,\n        height * this._deviceCellHeight);\n    } else {\n      this._ctx.fillStyle = this._themeService.colors.background.css;\n      this._ctx.fillRect(\n        x * this._deviceCellWidth,\n        y * this._deviceCellHeight,\n        width * this._deviceCellWidth,\n        height * this._deviceCellHeight);\n    }\n  }\n\n  /**\n   * Draws a truecolor character at the cell. The character will be clipped to\n   * ensure that it fits with the cell, including the cell to the right if it's\n   * a wide character. This uses the existing fillStyle on the context.\n   * @param terminal The terminal.\n   * @param cell The cell data for the character to draw.\n   * @param x The column to draw at.\n   * @param y The row to draw at.\n   */\n  protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void {\n    this._ctx.font = this._getFont(terminal, false, false);\n    this._ctx.textBaseline = TEXT_BASELINE;\n    this._clipCell(x, y, cell.getWidth());\n    this._ctx.fillText(\n      cell.getChars(),\n      x * this._deviceCellWidth + this._deviceCharLeft,\n      y * this._deviceCellHeight + this._deviceCharTop + this._deviceCharHeight);\n  }\n\n  /**\n   * Clips a cell to ensure no pixels will be drawn outside of it.\n   * @param x The column to clip.\n   * @param y The row to clip.\n   * @param width The number of columns to clip.\n   */\n  private _clipCell(x: number, y: number, width: number): void {\n    this._ctx.beginPath();\n    this._ctx.rect(\n      x * this._deviceCellWidth,\n      y * this._deviceCellHeight,\n      width * this._deviceCellWidth,\n      this._deviceCellHeight);\n    this._ctx.clip();\n  }\n\n  /**\n   * Gets the current font.\n   * @param terminal The terminal.\n   * @param isBold If we should use the bold fontWeight.\n   */\n  protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string {\n    const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;\n    const fontStyle = isItalic ? 'italic' : '';\n\n    return `${fontStyle} ${fontWeight} ${terminal.options.fontSize! * this._coreBrowserService.dpr}px ${terminal.options.fontFamily}`;\n  }\n}\n\n"
  },
  {
    "path": "addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { is256Color } from '../CharAtlasUtils';\nimport { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport { ICoreBrowserService, IThemeService } from 'browser/services/Services';\nimport { ILinkifier2, ILinkifierEvent } from 'browser/Types';\nimport { IOptionsService } from 'common/services/Services';\nimport { Terminal } from '@xterm/xterm';\nimport { BaseRenderLayer } from './BaseRenderLayer';\n\nexport class LinkRenderLayer extends BaseRenderLayer {\n  private _state: ILinkifierEvent | undefined;\n\n  constructor(\n    container: HTMLElement,\n    zIndex: number,\n    terminal: Terminal,\n    linkifier2: ILinkifier2,\n    coreBrowserService: ICoreBrowserService,\n    optionsService: IOptionsService,\n    themeService: IThemeService\n  ) {\n    super(terminal, container, 'link', zIndex, true, coreBrowserService, optionsService, themeService);\n\n    this._register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e)));\n    this._register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e)));\n  }\n\n  public resize(terminal: Terminal, dim: IRenderDimensions): void {\n    super.resize(terminal, dim);\n    // Resizing the canvas discards the contents of the canvas so clear state\n    this._state = undefined;\n  }\n\n  public reset(terminal: Terminal): void {\n    this._clearCurrentLink();\n  }\n\n  private _clearCurrentLink(): void {\n    if (this._state) {\n      this._clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);\n      const middleRowCount = this._state.y2 - this._state.y1 - 1;\n      if (middleRowCount > 0) {\n        this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);\n      }\n      this._clearCells(0, this._state.y2, this._state.x2, 1);\n      this._state = undefined;\n    }\n  }\n\n  private _handleShowLinkUnderline(e: ILinkifierEvent): void {\n    if (e.fg === INVERTED_DEFAULT_COLOR) {\n      this._ctx.fillStyle = this._themeService.colors.background.css;\n    } else if (e.fg !== undefined && is256Color(e.fg)) {\n      // 256 color support\n      this._ctx.fillStyle = this._themeService.colors.ansi[e.fg!].css;\n    } else {\n      this._ctx.fillStyle = this._themeService.colors.foreground.css;\n    }\n\n    if (e.y1 === e.y2) {\n      // Single line link\n      this._fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);\n    } else {\n      // Multi-line link\n      this._fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);\n      for (let y = e.y1 + 1; y < e.y2; y++) {\n        this._fillBottomLineAtCells(0, y, e.cols);\n      }\n      this._fillBottomLineAtCells(0, e.y2, e.x2);\n    }\n    this._state = e;\n  }\n\n  private _handleHideLinkUnderline(e: ILinkifierEvent): void {\n    this._clearCurrentLink();\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/renderLayer/Types.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable, Terminal } from '@xterm/xterm';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\n\nexport interface IRenderLayer extends IDisposable {\n  /**\n   * Called when the terminal loses focus.\n   */\n  handleBlur(terminal: Terminal): void;\n\n  /**\n   * Called when the terminal gets focus.\n   */\n  handleFocus(terminal: Terminal): void;\n\n  /**\n   * Called when the cursor is moved.\n   */\n  handleCursorMove(terminal: Terminal): void;\n\n  /**\n   * Called when the data in the grid has changed (or needs to be rendered\n   * again).\n   */\n  handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void;\n\n  /**\n   * Calls when the selection changes.\n   */\n  handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;\n\n  /**\n   * Registers a handler to join characters to render as a group\n   */\n  registerCharacterJoiner?(handler: (text: string) => [number, number][]): void;\n\n  /**\n   * Deregisters the specified character joiner handler\n   */\n  deregisterCharacterJoiner?(joinerId: number): void;\n\n  /**\n   * Resize the render layer.\n   */\n  resize(terminal: Terminal, dim: IRenderDimensions): void;\n\n  /**\n   * Clear the state of the render layer.\n   */\n  reset(terminal: Terminal): void;\n}\n"
  },
  {
    "path": "addons/addon-webgl/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"lib\": [\n      \"dom\",\n      \"es2021\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"@xterm/addon-webgl\": [\n        \"../typings/addon-webgl.d.ts\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"types\": [\n      \"../../../node_modules/@types/mocha\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-webgl/test/WebglRenderer.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport test from '@playwright/test';\nimport { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../test/playwright/SharedRendererTests';\nimport { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';\nimport { platform } from 'os';\n\nlet ctx: ITestContext;\nconst ctxWrapper: ISharedRendererTestContext = { value: undefined } as any;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n  ctxWrapper.value = ctx;\n  await ctx.page.evaluate(`\n    window.addon = new window.WebglAddon(true);\n    window.term.loadAddon(window.addon);\n  `);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('WebGL Renderer Integration Tests', async () => {\n  // HACK: webgl2 is often not supported in headless firefox on Linux\n  // https://github.com/microsoft/playwright/issues/11566\n  if (platform() === 'linux') {\n    test.skip(({ browserName }) => browserName === 'firefox');\n  }\n\n  injectSharedRendererTests(ctxWrapper);\n  injectSharedRendererTestsStandalone(ctxWrapper, async () => {\n    await ctx.page.evaluate(`\n      window.addon = new window.WebglAddon(true);\n      window.term.loadAddon(window.addon);\n    `);\n  });\n});\n"
  },
  {
    "path": "addons/addon-webgl/test/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm run start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "addons/addon-webgl/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"ESNext\",\n    \"lib\": [\n      \"es2021\",\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../out-test\",\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"paths\": {\n      \"common/*\": [\n        \"../../../src/common/*\"\n      ],\n      \"browser/*\": [\n        \"../../../src/browser/*\"\n      ],\n      \"*\": [\n        \"./*\"\n      ]\n    },\n    \"strict\": true,\n    \"types\": [\n      \"../../../node_modules/@types/node\",\n      \"../../../node_modules/@lunapaint/png-codec\"\n    ]\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../../src/common\"\n    },\n    {\n      \"path\": \"../../../src/browser\"\n    },\n    {\n      \"path\": \"../../../test/playwright\"\n    }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-webgl/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./src\" },\n    { \"path\": \"./test\" }\n  ]\n}\n"
  },
  {
    "path": "addons/addon-webgl/typings/addon-webgl.d.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';\n\ndeclare module '@xterm/addon-webgl' {\n  /**\n   * An xterm.js addon that provides hardware-accelerated rendering functionality via WebGL.\n   */\n  export class WebglAddon implements ITerminalAddon {\n    public textureAtlas?: HTMLCanvasElement;\n\n    /**\n     * An event that is fired when the renderer loses its canvas context.\n     */\n    public readonly onContextLoss: IEvent<void>;\n\n    /**\n     * An event that is fired when the texture atlas of the renderer changes.\n     */\n    public readonly onChangeTextureAtlas: IEvent<HTMLCanvasElement>;\n\n    /**\n     * An event that is fired when the a new page is added to the texture atlas.\n     */\n    public readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;\n\n    /**\n     * An event that is fired when the a page is removed from the texture atlas.\n     */\n    public readonly onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;\n\n    constructor(options?: IWebglAddonOptions);\n\n    /**\n     * Activates the addon.\n     * @param terminal The terminal the addon is being loaded in.\n     */\n    public activate(terminal: Terminal): void;\n\n    /**\n     * Disposes the addon.\n     */\n    public dispose(): void;\n\n    /**\n     * Clears the terminal's texture atlas and triggers a redraw.\n     */\n    public clearTextureAtlas(): void;\n  }\n\n  export interface IWebglAddonOptions {\n    /**\n     * Whether to draw custom glyphs instead of using the font for the following\n     * unicode ranges:\n     *\n     * - Box Drawing (U+2500-U+257F)\n     * - Block Elements (U+2580-U+259F)\n     * - Braille Patterns (U+2800-U+28FF)\n     * - Powerline Symbols (U+E0A0-U+E0D4, Private Use Area with widespread\n     *   adoption)\n     * - Progress Indicators (U+EE00-U+EE0B, Private Use Area initially added in\n     *   [Fira Code](https://github.com/tonsky/FiraCode) and later\n     *   [Nerd Fonts](https://github.com/ryanoasis/nerd-fonts/pull/1733)).\n     * - Git Branch Symbols (U+F5D0-U+F60D, Private Use Area initially adopted\n     *   in [Kitty in 2024](https://github.com/kovidgoyal/kitty/pull/7681) by\n     *   author of [vim-flog](https://github.com/rbong/vim-flog))\n     * - Symbols for Legacy Computing (U+1FB00-U+1FBFF)\n     *\n     * This will typically result in better rendering with continuous lines,\n     * even when line height and letter spacing is used. The default is true.\n     */\n    customGlyphs?: boolean;\n\n    /**\n     * Whether to enable the preserveDrawingBuffer flag when creating the WebGL\n     * context. This may be useful in tests. This default is false.\n     */\n    preserveDrawingBuffer?: boolean\n  }\n}\n"
  },
  {
    "path": "addons/addon-webgl/webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\nconst addonName = 'WebglAddon';\nconst mainFile = 'addon-webgl.js';\n\nmodule.exports = {\n  entry: `./out/${addonName}.js`,\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('../../out/common'),\n      browser: path.resolve('../../out/browser'),\n      vs: path.resolve('../../out/vs')\n    }\n  },\n  output: {\n    filename: mainFile,\n    path: path.resolve('./lib'),\n    library: addonName,\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production'\n};\n"
  },
  {
    "path": "bin/agent/setup-repo.mjs",
    "content": "// @ts-check\n\nimport { cpSync, existsSync, lstatSync, readFileSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { basename, dirname, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');\nconst nodeModulesPath = resolve(repoRoot, 'node_modules');\nconst npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n\n/** @typedef {{ folder: string; reason: string }} Candidate */\n\n/** @param {string} message */\nfunction log(message) {\n  console.info(`[setup-fast] ${message}`);\n}\n\n/** @param {string[]} args */\nfunction runNpm(args) {\n  log(`Running: npm ${args.join(' ')}`);\n  const result = spawnSync(npmExecutable, args, {\n    cwd: repoRoot,\n    stdio: 'inherit'\n  });\n  if (result.error) {\n    throw result.error;\n  }\n  if ((result.status ?? 1) !== 0) {\n    process.exit(result.status ?? 1);\n  }\n}\n\n/**\n * @param {Candidate[]} candidates\n * @param {string | undefined} folder\n * @param {string} reason\n */\nfunction addCandidate(candidates, folder, reason) {\n  if (!folder) {\n    return;\n  }\n  if (candidates.some(candidate => candidate.folder === folder)) {\n    return;\n  }\n  candidates.push({ folder, reason });\n  log(`Candidate found (${reason}): ${folder}`);\n}\n\nfunction detectMainSiblingFolder() {\n  const currentFolderName = basename(repoRoot);\n  if (!currentFolderName.startsWith('xterm.js') || currentFolderName === 'xterm.js') {\n    log(`Current folder is \"${currentFolderName}\", skipping xterm.js sibling lookup.`);\n    return undefined;\n  }\n  const siblingFolder = resolve(dirname(repoRoot), 'xterm.js');\n  log(`Current folder is \"${currentFolderName}\", sibling main folder candidate: ${siblingFolder}`);\n  return siblingFolder;\n}\n\nfunction detectWorktreeMainFolder() {\n  const gitPath = resolve(repoRoot, '.git');\n  if (!existsSync(gitPath)) {\n    log('No .git entry found at repo root.');\n    return undefined;\n  }\n  const gitStat = lstatSync(gitPath);\n  if (!gitStat.isFile()) {\n    log('.git is not a file, this repo does not appear to be a worktree checkout.');\n    return undefined;\n  }\n  const gitFileContent = readFileSync(gitPath, 'utf8').trim();\n  const gitDirMatch = /^gitdir:\\s*(.+)$/m.exec(gitFileContent);\n  if (!gitDirMatch) {\n    log('Could not parse gitdir from .git file.');\n    return undefined;\n  }\n\n  const gitDirPath = resolve(repoRoot, gitDirMatch[1].trim());\n  log(`Parsed gitdir from .git file: ${gitDirPath}`);\n\n  const normalizedGitDirPath = gitDirPath.replace(/\\\\/g, '/');\n  const worktreeMarker = '/.git/worktrees/';\n  const markerIndex = normalizedGitDirPath.indexOf(worktreeMarker);\n  if (markerIndex === -1) {\n    log('gitdir path does not contain /.git/worktrees/, skipping worktree main folder lookup.');\n    return undefined;\n  }\n\n  const normalizedMainFolder = normalizedGitDirPath.slice(0, markerIndex);\n  const mainFolder = sep === '/' ? normalizedMainFolder : normalizedMainFolder.split('/').join(sep);\n  log(`Worktree main folder candidate: ${mainFolder}`);\n  return mainFolder;\n}\n\nfunction resolveSourceFolder() {\n  /** @type {Candidate[]} */\n  const candidates = [];\n  addCandidate(candidates, detectMainSiblingFolder(), 'xterm.js sibling');\n  addCandidate(candidates, detectWorktreeMainFolder(), 'worktree main repo');\n\n  for (const candidate of candidates) {\n    const candidateNodeModulesPath = resolve(candidate.folder, 'node_modules');\n    if (existsSync(candidateNodeModulesPath)) {\n      log(`Using candidate (${candidate.reason}) with node_modules: ${candidate.folder}`);\n      return candidate.folder;\n    }\n    log(`Candidate skipped (${candidate.reason}), node_modules missing: ${candidateNodeModulesPath}`);\n  }\n\n  log('No candidate folder with node_modules was found.');\n  return undefined;\n}\n\nif (!existsSync(nodeModulesPath)) {\n  log(`node_modules missing: ${nodeModulesPath}`);\n  const sourceFolder = resolveSourceFolder();\n  if (sourceFolder) {\n    const sourceNodeModulesPath = resolve(sourceFolder, 'node_modules');\n    log(`Copying node_modules from ${sourceNodeModulesPath} to ${nodeModulesPath}`);\n    try {\n      cpSync(sourceNodeModulesPath, nodeModulesPath, { recursive: true });\n      log('node_modules copy completed.');\n    } catch (error) {\n      const message = error instanceof Error ? error.message : String(error);\n      log(`node_modules copy failed: ${message}`);\n      log('Falling back to npm ci.');\n      runNpm(['ci']);\n    }\n  } else {\n    log('No source folder available, running npm ci.');\n    runNpm(['ci']);\n  }\n} else {\n  log(`node_modules already exists: ${nodeModulesPath}`);\n}\n\nrunNpm(['run', 'setup']);\n"
  },
  {
    "path": "bin/convert_svg_to_custom_glyph.js",
    "content": "/**\n * Converts an SVG file as exported by fontforge into the SVG-like format as expected by the custom\n * glyph rasterizer.\n *\n * Usage: node convert_svg_to_custom_glyph.js <svg-file-or-folder>\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst input = process.argv[2];\nif (!input) {\n  console.error('Usage: node convert_svg_to_custom_glyph.js <svg-file-or-folder>');\n  process.exit(1);\n}\n\nconst inputPath = path.resolve(process.cwd(), input);\nconst stat = fs.statSync(inputPath);\nconst files = stat.isDirectory()\n  ? fs.readdirSync(inputPath).filter(f => f.endsWith('.svg')).map(f => path.join(inputPath, f))\n  : [inputPath];\n\nif (files.length === 0) {\n  console.error('No SVG files found');\n  process.exit(1);\n}\n\nfor (const file of files) {\n  console.log(`\\n${'='.repeat(60)}\\nProcessing: ${path.basename(file)}\\n${'='.repeat(60)}`);\n  processFile(file);\n}\n\nfunction processFile(filePath) {\n  // Get file content\n  const content = fs.readFileSync(filePath, 'utf8');\n\n  // Get viewBox\n  const viewBoxMatch = content.match(/viewBox=\"([^\"]+)\"/);\n  if (!viewBoxMatch) {\n    console.error('No viewBox found in SVG');\n    return;\n  }\n  const [minX, minY, width, height] = viewBoxMatch[1].split(/\\s+/).map(Number);\n  console.log(`ViewBox: ${minX} ${minY} ${width} ${height}`);\n\n  // Get path `d` property\n  const pathMatch = content.match(/<path[^>]*\\sd=\"([^\"]+)\"/);\n  if (!pathMatch) {\n    console.error('No path d attribute found in SVG');\n    return;\n  }\n  const originalPath = pathMatch[1].replace(/\\s+/g, ' ').trim();\n  console.log(`\\nOriginal path length: ${originalPath.length} chars`);\n\n  // Parse path into commands\n  function parsePath(d) {\n  const commands = [];\n  const regex = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;\n  let match;\n  while ((match = regex.exec(d)) !== null) {\n    const cmd = match[1];\n    const argsStr = match[2].trim();\n    const args = argsStr ? argsStr.split(/[\\s,]+/).map(Number) : [];\n    commands.push({ cmd, args });\n  }\n  return commands;\n}\n\n// Convert relative commands to absolute and expand T/S to Q/C\nfunction toAbsolute(commands) {\n  const result = [];\n  let x = 0, y = 0; // Current position\n  let startX = 0, startY = 0; // Start of current subpath\n  let lastControlX = 0, lastControlY = 0; // Last control point for T/S\n  let lastCmd = '';\n\n  for (const { cmd, args } of commands) {\n    const isRelative = cmd === cmd.toLowerCase();\n    const absCmd = cmd.toUpperCase();\n\n    switch (absCmd) {\n      case 'M': {\n        // MoveTo: M x y (or m dx dy)\n        const absArgs = [];\n        for (let i = 0; i < args.length; i += 2) {\n          const newX = isRelative ? x + args[i] : args[i];\n          const newY = isRelative ? y + args[i + 1] : args[i + 1];\n          absArgs.push(newX, newY);\n          x = newX;\n          y = newY;\n          if (i === 0) {\n            startX = x;\n            startY = y;\n          }\n        }\n        lastControlX = x;\n        lastControlY = y;\n        result.push({ cmd: 'M', args: absArgs });\n        break;\n      }\n      case 'L': {\n        // LineTo: L x y (or l dx dy)\n        const absArgs = [];\n        for (let i = 0; i < args.length; i += 2) {\n          const newX = isRelative ? x + args[i] : args[i];\n          const newY = isRelative ? y + args[i + 1] : args[i + 1];\n          absArgs.push(newX, newY);\n          x = newX;\n          y = newY;\n        }\n        lastControlX = x;\n        lastControlY = y;\n        result.push({ cmd: 'L', args: absArgs });\n        break;\n      }\n      case 'H': {\n        // Horizontal LineTo - convert to L\n        for (let i = 0; i < args.length; i++) {\n          const newX = isRelative ? x + args[i] : args[i];\n          result.push({ cmd: 'L', args: [newX, y] });\n          x = newX;\n        }\n        lastControlX = x;\n        lastControlY = y;\n        break;\n      }\n      case 'V': {\n        // Vertical LineTo - convert to L\n        for (let i = 0; i < args.length; i++) {\n          const newY = isRelative ? y + args[i] : args[i];\n          result.push({ cmd: 'L', args: [x, newY] });\n          y = newY;\n        }\n        lastControlX = x;\n        lastControlY = y;\n        break;\n      }\n      case 'C': {\n        // CurveTo: C x1 y1 x2 y2 x y (or c dx1 dy1 dx2 dy2 dx dy)\n        const absArgs = [];\n        for (let i = 0; i < args.length; i += 6) {\n          const x1 = isRelative ? x + args[i] : args[i];\n          const y1 = isRelative ? y + args[i + 1] : args[i + 1];\n          const x2 = isRelative ? x + args[i + 2] : args[i + 2];\n          const y2 = isRelative ? y + args[i + 3] : args[i + 3];\n          const newX = isRelative ? x + args[i + 4] : args[i + 4];\n          const newY = isRelative ? y + args[i + 5] : args[i + 5];\n          absArgs.push(x1, y1, x2, y2, newX, newY);\n          lastControlX = x2;\n          lastControlY = y2;\n          x = newX;\n          y = newY;\n        }\n        result.push({ cmd: 'C', args: absArgs });\n        break;\n      }\n      case 'S': {\n        // Smooth CurveTo - expand to C\n        for (let i = 0; i < args.length; i += 4) {\n          // Reflect last control point\n          let x1, y1;\n          if (lastCmd === 'C' || lastCmd === 'S') {\n            x1 = 2 * x - lastControlX;\n            y1 = 2 * y - lastControlY;\n          } else {\n            x1 = x;\n            y1 = y;\n          }\n          const x2 = isRelative ? x + args[i] : args[i];\n          const y2 = isRelative ? y + args[i + 1] : args[i + 1];\n          const newX = isRelative ? x + args[i + 2] : args[i + 2];\n          const newY = isRelative ? y + args[i + 3] : args[i + 3];\n          result.push({ cmd: 'C', args: [x1, y1, x2, y2, newX, newY] });\n          lastControlX = x2;\n          lastControlY = y2;\n          x = newX;\n          y = newY;\n        }\n        break;\n      }\n      case 'Q': {\n        // Quadratic CurveTo: Q x1 y1 x y (or q dx1 dy1 dx dy)\n        const absArgs = [];\n        for (let i = 0; i < args.length; i += 4) {\n          const x1 = isRelative ? x + args[i] : args[i];\n          const y1 = isRelative ? y + args[i + 1] : args[i + 1];\n          const newX = isRelative ? x + args[i + 2] : args[i + 2];\n          const newY = isRelative ? y + args[i + 3] : args[i + 3];\n          absArgs.push(x1, y1, newX, newY);\n          lastControlX = x1;\n          lastControlY = y1;\n          x = newX;\n          y = newY;\n        }\n        result.push({ cmd: 'Q', args: absArgs });\n        break;\n      }\n      case 'T': {\n        // Smooth Quadratic CurveTo - keep as T\n        const absArgs = [];\n        for (let i = 0; i < args.length; i += 2) {\n          // Reflect last control point for tracking\n          let cpX, cpY;\n          if (lastCmd === 'Q' || lastCmd === 'T') {\n            cpX = 2 * x - lastControlX;\n            cpY = 2 * y - lastControlY;\n          } else {\n            cpX = x;\n            cpY = y;\n          }\n          const newX = isRelative ? x + args[i] : args[i];\n          const newY = isRelative ? y + args[i + 1] : args[i + 1];\n          absArgs.push(newX, newY);\n          lastControlX = cpX;\n          lastControlY = cpY;\n          x = newX;\n          y = newY;\n          lastCmd = 'T'; // For chained T commands\n        }\n        result.push({ cmd: 'T', args: absArgs });\n        break;\n      }\n      case 'A': {\n        // Arc: A rx ry x-axis-rotation large-arc-flag sweep-flag x y\n        const absArgs = [];\n        for (let i = 0; i < args.length; i += 7) {\n          const rx = args[i];\n          const ry = args[i + 1];\n          const rotation = args[i + 2];\n          const largeArc = args[i + 3];\n          const sweep = args[i + 4];\n          const newX = isRelative ? x + args[i + 5] : args[i + 5];\n          const newY = isRelative ? y + args[i + 6] : args[i + 6];\n          absArgs.push(rx, ry, rotation, largeArc, sweep, newX, newY);\n          x = newX;\n          y = newY;\n        }\n        lastControlX = x;\n        lastControlY = y;\n        result.push({ cmd: 'A', args: absArgs });\n        break;\n      }\n      case 'Z': {\n        // ClosePath\n        x = startX;\n        y = startY;\n        lastControlX = x;\n        lastControlY = y;\n        result.push({ cmd: 'Z', args: [] });\n        break;\n      }\n    }\n\n    if (absCmd !== 'T') {\n      lastCmd = absCmd;\n    }\n  }\n\n  return result;\n}\n\n// Scale coordinates to 0-1 range\nfunction scaleToNormalized(commands, minX, minY, width, height) {\n  function scaleX(val) {\n    return (val - minX) / width;\n  }\n  function scaleY(val) {\n    return (val - minY) / height;\n  }\n  function scaleRx(val) {\n    return val / width;\n  }\n  function scaleRy(val) {\n    return val / height;\n  }\n\n  const result = [];\n  for (const { cmd, args } of commands) {\n    const scaledArgs = [];\n\n    switch (cmd) {\n      case 'M':\n      case 'L':\n      case 'T': {\n        for (let i = 0; i < args.length; i += 2) {\n          scaledArgs.push(scaleX(args[i]), scaleY(args[i + 1]));\n        }\n        break;\n      }\n      case 'H': {\n        for (let i = 0; i < args.length; i++) {\n          scaledArgs.push(scaleX(args[i]));\n        }\n        break;\n      }\n      case 'V': {\n        for (let i = 0; i < args.length; i++) {\n          scaledArgs.push(scaleY(args[i]));\n        }\n        break;\n      }\n      case 'C': {\n        for (let i = 0; i < args.length; i += 6) {\n          scaledArgs.push(\n            scaleX(args[i]), scaleY(args[i + 1]),\n            scaleX(args[i + 2]), scaleY(args[i + 3]),\n            scaleX(args[i + 4]), scaleY(args[i + 5])\n          );\n        }\n        break;\n      }\n      case 'S':\n      case 'Q': {\n        for (let i = 0; i < args.length; i += 4) {\n          scaledArgs.push(\n            scaleX(args[i]), scaleY(args[i + 1]),\n            scaleX(args[i + 2]), scaleY(args[i + 3])\n          );\n        }\n        break;\n      }\n      case 'A': {\n        for (let i = 0; i < args.length; i += 7) {\n          // rx, ry need to be scaled; rotation and flags stay the same\n          scaledArgs.push(\n            scaleRx(args[i]),     // rx\n            scaleRy(args[i + 1]), // ry\n            args[i + 2],          // rotation\n            args[i + 3],          // large-arc\n            args[i + 4],          // sweep\n            scaleX(args[i + 5]),  // x\n            scaleY(args[i + 6])   // y\n          );\n        }\n        break;\n      }\n      case 'Z': {\n        // No args\n        break;\n      }\n    }\n\n    result.push({ cmd, args: scaledArgs });\n  }\n\n  return result;\n}\n\n// Format number to reasonable precision\nfunction formatNum(n, precision = 4) {\n  const rounded = Number(n.toFixed(precision));\n  return String(rounded);\n}\n\n// Convert commands back to path string\nfunction commandsToPath(commands) {\n  return commands.map(({ cmd, args }, i) => {\n    const prefix = i === 0 ? '' : ' ';\n    if (args.length === 0) return prefix + cmd;\n    return prefix + cmd + args.map(a => formatNum(a)).join(',');\n  }).join('');\n}\n\n  // Main conversion\n  const parsed = parsePath(originalPath);\n  const absolute = toAbsolute(parsed);\n\n  // Calculate actual bounding box from path data\n  function getBoundingBox(commands) {\n  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\n  for (const { cmd, args } of commands) {\n    switch (cmd) {\n      case 'M':\n      case 'L':\n      case 'T': {\n        for (let i = 0; i < args.length; i += 2) {\n          minX = Math.min(minX, args[i]);\n          maxX = Math.max(maxX, args[i]);\n          minY = Math.min(minY, args[i + 1]);\n          maxY = Math.max(maxY, args[i + 1]);\n        }\n        break;\n      }\n      case 'H': {\n        for (let i = 0; i < args.length; i++) {\n          minX = Math.min(minX, args[i]);\n          maxX = Math.max(maxX, args[i]);\n        }\n        break;\n      }\n      case 'V': {\n        for (let i = 0; i < args.length; i++) {\n          minY = Math.min(minY, args[i]);\n          maxY = Math.max(maxY, args[i]);\n        }\n        break;\n      }\n      case 'C': {\n        for (let i = 0; i < args.length; i += 6) {\n          // Include control points and endpoint\n          minX = Math.min(minX, args[i], args[i + 2], args[i + 4]);\n          maxX = Math.max(maxX, args[i], args[i + 2], args[i + 4]);\n          minY = Math.min(minY, args[i + 1], args[i + 3], args[i + 5]);\n          maxY = Math.max(maxY, args[i + 1], args[i + 3], args[i + 5]);\n        }\n        break;\n      }\n      case 'S':\n      case 'Q': {\n        for (let i = 0; i < args.length; i += 4) {\n          minX = Math.min(minX, args[i], args[i + 2]);\n          maxX = Math.max(maxX, args[i], args[i + 2]);\n          minY = Math.min(minY, args[i + 1], args[i + 3]);\n          maxY = Math.max(maxY, args[i + 1], args[i + 3]);\n        }\n        break;\n      }\n      case 'A': {\n        for (let i = 0; i < args.length; i += 7) {\n          minX = Math.min(minX, args[i + 5]);\n          maxX = Math.max(maxX, args[i + 5]);\n          minY = Math.min(minY, args[i + 6]);\n          maxY = Math.max(maxY, args[i + 6]);\n        }\n        break;\n      }\n    }\n  }\n\n  return { minX, minY, width: maxX - minX, height: maxY - minY };\n}\n\n  const bbox = getBoundingBox(absolute);\n  console.log(`Path bounding box: x=${bbox.minX}, y=${bbox.minY}, w=${bbox.width}, h=${bbox.height}`);\n\n  // Use viewBox for normalization (not path bounding box)\n  const normalized = scaleToNormalized(absolute, minX, minY, width, height);\n  const result = commandsToPath(normalized);\n\n  console.log(`\\nConverted path (${result.length} chars):\\n`);\n  console.log(result);\n\n  console.log(`\\n\\nFor CustomGlyphDefinitions.ts:\\n`);\n  console.log(`'\\\\u{E0C0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: '${result}', type: CustomGlyphVectorType.FILL } },`);\n\n  // Write output file\n  const ext = path.extname(filePath);\n  const outputPath = filePath.replace(ext, `_output${ext}`);\n  const svgOutput = `<?xml version=\"1.0\" standalone=\"no\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 1 1\">\n  <path fill=\"currentColor\" d=\"${result}\" />\n</svg>\n`;\n  fs.writeFileSync(outputPath, svgOutput, 'utf8');\n  console.log(`\\nOutput written to: ${outputPath}`);\n}\n"
  },
  {
    "path": "bin/esbuild.mjs",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// @ts-check\n\nimport { build, context, default as esbuild } from 'esbuild';\nimport { argv } from 'process';\n\nconst config = {\n  isProd: argv.includes('--prod'),\n  isWatch: argv.includes('--watch'),\n  isDemoClient: argv.includes('--demo-client'),\n  isDemoServer: argv.includes('--demo-server'),\n  isHeadless: argv.includes('--headless'),\n  addon: argv.find(e => e.startsWith('--addon='))?.replace(/^--addon=/, ''),\n};\n\n// console.info('Running with config:', JSON.stringify(config, undefined, 2));\n\n/** @type {esbuild.BuildOptions} */\nconst commonOptions = {\n  bundle: true,\n  format: 'esm',\n  target: 'es2021',\n  sourcemap: true,\n  treeShaking: true,\n  logLevel: 'warning',\n};\n\n/** @type {esbuild.BuildOptions} */\nconst devOptions = {\n  minify: false,\n};\n\n/** @type {esbuild.BuildOptions} */\nconst prodOptions = {\n  minify: true,\n  legalComments: 'none',\n  // TODO: Mangling private and protected properties will reduce bundle size quite a bit, we must\n  //       make sure we don't cast privates to `any` in order to prevent regressions.\n  //mangleProps: /_.+/,\n  banner: {\n    js: `/**\n * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n *   Fabrice Bellard's javascript vt100 for jslinux:\n *   http://bellard.org/jslinux/\n *   Copyright (c) 2011 Fabrice Bellard\n */\n/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/`\n  },\n};\n\n/**\n * @param {string} addon\n */\nfunction getAddonEntryPoint(addon) {\n  let result = '';\n  let nextCap = true;\n  for (const char of addon) {\n    if (char === '-') {\n      nextCap = true;\n      continue;\n    }\n    result += nextCap ? char.toUpperCase() : char\n    nextCap = false;\n  }\n  result += 'Addon';\n  return result;\n}\n\n/** @type {esbuild.BuildOptions} */\nlet bundleConfig = {\n  ...commonOptions,\n  ...(config.isProd ? prodOptions : devOptions)\n};\n\n/** @type {esbuild.BuildOptions} */\nlet outConfig = {\n  format: 'cjs',\n  sourcemap: true,\n}\nlet skipOut = false;\n\n/** @type {esbuild.BuildOptions} */\nlet outTestConfig = {\n  format: 'cjs',\n  sourcemap: true,\n}\nlet skipOutTest = false;\n\nif (config.addon) {\n  bundleConfig = {\n    ...bundleConfig,\n    entryPoints: [`addons/addon-${config.addon}/src/${getAddonEntryPoint(config.addon)}.ts`],\n    outfile: `addons/addon-${config.addon}/lib/addon-${config.addon}.mjs`,\n  };\n  outConfig = {\n    ...outConfig,\n    entryPoints: [`addons/addon-${config.addon}/src/**/*.ts`],\n    outdir: `addons/addon-${config.addon}/out-esbuild/`\n  };\n  outTestConfig = {\n    ...outConfig,\n    entryPoints: [`addons/addon-${config.addon}/test/**/*.ts`],\n    outdir: `addons/addon-${config.addon}/out-esbuild-test/`\n  };\n\n  if (config.addon === 'ligatures') {\n    bundleConfig.platform = 'node';\n    skipOutTest = true;\n  }\n\n  if (config.addon === 'serialize') {\n    bundleConfig.tsconfig = 'addons/addon-serialize/src/tsconfig.json'\n  }\n} else if (config.isDemoClient) {\n  bundleConfig = {\n    ...bundleConfig,\n    sourcemap: false,\n    entryPoints: [`demo/client/client.ts`],\n    outfile: 'demo/dist/client-bundle.js',\n    external: ['util', 'os', 'fs', 'path', 'stream', 'Terminal'],\n    alias: {\n      // Library ESM imports\n      \"@xterm/xterm\": \".\",\n      \"@xterm/addon-attach\": \"./addons/addon-attach/lib/addon-attach.mjs\",\n      \"@xterm/addon-clipboard\": \"./addons/addon-clipboard/lib/addon-clipboard.mjs\",\n      \"@xterm/addon-fit\": \"./addons/addon-fit/lib/addon-fit.mjs\",\n      \"@xterm/addon-image\": \"./addons/addon-image/lib/addon-image.mjs\",\n      \"@xterm/addon-progress\": \"./addons/addon-progress/lib/addon-progress.mjs\",\n      \"@xterm/addon-search\": \"./addons/addon-search/lib/addon-search.mjs\",\n      \"@xterm/addon-serialize\": \"./addons/addon-serialize/lib/addon-serialize.mjs\",\n      \"@xterm/addon-web-fonts\": \"./addons/addon-web-fonts/lib/addon-web-fonts.mjs\",\n      \"@xterm/addon-web-links\": \"./addons/addon-web-links/lib/addon-web-links.mjs\",\n      \"@xterm/addon-webgl\": \"./addons/addon-webgl/lib/addon-webgl.mjs\",\n      \"@xterm/addon-unicode11\": \"./addons/addon-unicode11/lib/addon-unicode11.mjs\",\n      \"@xterm/addon-unicode-graphemes\": \"./addons/addon-unicode-graphemes/lib/addon-unicode-graphemes.mjs\",\n\n      // Non-bundled ESM imports\n      // HACK: Ligatures imports fs which in the esbuild bundle resolves at runtime _on startup_\n      //       instead of only when it's needed. This causes a `Dynamic require of \"fs\" is not\n      //       supported` exception to be thrown. So the unbundled out-esbuild sources are used\n      //       instead of the .mjs file which seems to resolve the issue.\n      \"@xterm/addon-ligatures\": \"./addons/addon-ligatures/out-esbuild/LigaturesAddon\",\n    }\n  }\n  skipOut = true;\n  skipOutTest = true;\n} else if (config.isDemoServer) {\n  bundleConfig = {\n    ...bundleConfig,\n    entryPoints: [`demo/server/server.ts`],\n    outfile: 'demo/dist/server-bundle.js',\n    format: 'cjs',\n    platform: 'node',\n    external: ['node-pty'],\n  }\n  skipOut = true;\n  skipOutTest = true;\n} else if (config.isHeadless) {\n  bundleConfig = {\n    ...bundleConfig,\n    entryPoints: [`src/headless/public/Terminal.ts`],\n    outfile: `headless/lib-headless/xterm-headless.mjs`\n  };\n  outConfig = {\n    ...outConfig,\n    entryPoints: ['src/**/*.ts'],\n    outdir: 'out-esbuild/'\n  };\n  skipOut = true;\n} else {\n  bundleConfig = {\n    ...bundleConfig,\n    entryPoints: [`src/browser/public/Terminal.ts`],\n    outfile: `lib/xterm.mjs`\n  };\n  outConfig = {\n    ...outConfig,\n    entryPoints: [\n      'src/browser/**/*.ts',\n      'src/common/**/*.ts',\n      'src/headless/**/*.ts'\n    ],\n    outdir: 'out-esbuild/'\n  };\n  outTestConfig = {\n    ...outConfig,\n    entryPoints: ['test/**/*.ts'],\n    outdir: 'out-esbuild-test/'\n  };\n}\n\nif (config.isWatch) {\n  context(bundleConfig).then(e => e.watch());\n  if (!skipOut) {\n    context(outConfig).then(e => e.watch());\n  }\n  if (!skipOutTest) {\n    context(outTestConfig).then(e => e.watch());\n  }\n} else {\n  await build(bundleConfig);\n  if (!skipOut) {\n    await build(outConfig);\n  }\n  if (!skipOutTest) {\n    await build(outTestConfig);\n  }\n}\n"
  },
  {
    "path": "bin/esbuild_all.mjs",
    "content": "// @ts-check\n\nimport { spawn } from \"child_process\";\nimport { readdir } from \"fs/promises\";\nimport { argv } from \"process\";\n\n/** @type {{cp: import(\"child_process\").ChildProcessByStdio<any, any, any>, name: string}[]} */\nconst jobs = [];\n\n// Core job\njobs.push(createJob('xterm', []));\n\n// Addon jobs\nconst addons = (await readdir('addons')).filter(e => e.startsWith('addon-')).map(e => e.replace('addon-', ''));\nfor (const addon of addons) {\n  jobs.push(createJob(`xterm-addon-${addon}`, [`--addon=${addon}`]));\n}\n\n// Demo job - This requires the others to be built so it's not included when building all\n// jobs.push(createJob('demo-client', [`--demo-client`]));\n\nawait Promise.all(jobs.map((job, i) => {\n  return new Promise(r => {\n    job.cp.on('exit', code => {\n      log(`Finished \\x1b[32m${job.name}\\x1b[0m${code ? ' \\x1b[31mwith errors\\x1b[0m' : ''}`);\n      r(code);\n    });\n  });\n}));\n\n/**\n * @param {string} message\n */\nfunction log(message) {\n  console.info(`[\\x1b[2m${new Date().toLocaleTimeString('en-GB')}\\x1b[0m] ${message}`);\n}\n\n/**\n * @param {string} name\n * @param {string[]} extraArgs\n */\nfunction createJob(name, extraArgs) {\n  log(`Starting \\x1b[32m${name}\\x1b[0m...`);\n  const args = [\n    'bin/esbuild.mjs',\n    ...extraArgs,\n    ...argv\n  ];\n  return {\n    name,\n    cp: spawn('node', args, { stdio: [\"inherit\", \"inherit\", \"inherit\"] })\n  };\n}\n"
  },
  {
    "path": "bin/extract_vtfeatures.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Script to extract vt features documented in docstrings.\n */\nconst fs = require('fs');\nconst Mustache = require('mustache');\n\n/**\n * regexp to fetch all comments\n * Fetches all multiline comments and single lines containing '// @vt:'.\n */\nconst REX_COMMENTS = /^\\s*?\\/\\*\\*([\\S\\s]*?)\\*\\/|^\\s*?\\/\\/ (@vt:.*?)$/mug;\n\n/**\n * regexp to parse the @vt line\n * expected data - \"@vt: <status> <kind> <mnemonic> \"<name>\" \"<sequence>\" \"<short description>\"\n */\nconst REX_VT_LINE = /^@vt:\\s*(\\w+|#\\w+|#\\w+\\[.*?\\])\\s*(\\w+)\\s*(\\w+)\\s*\"(.*?)\"\\s*\"(.*?)\"\\s*\"(.*?)\".*$/;\n\n// known vt command types\nconst TYPES = [\n  'C0',\n  'C1',\n  'ESC',\n  'CSI',\n  'DCS',\n  'OSC',\n  'APC',\n  'PM',\n  'SOS'\n];\n\nconst MARKDOWN_TMPL = `---\ntitle: Supported Terminal Sequences\ncategory: API\n---\n\n{::options parse_block_html=\"true\" /}\n\n# Supported Terminal Sequences\n\nxterm.js version: {{version}}\n\n## Table of Contents\n\n<nav>\n\n- [General notes](#general-notes)\n{{#C0.length}}\n- [C0](#c0)\n{{/C0.length}}\n{{#C1.length}}\n- [C1](#c1)\n{{/C1.length}}\n{{#CSI.length}}\n- [CSI](#csi)\n{{/CSI.length}}\n{{#DCS.length}}\n- [DCS](#dcs)\n{{/DCS.length}}\n{{#ESC.length}}\n- [ESC](#esc)\n{{/ESC.length}}\n{{#OSC.length}}\n- [OSC](#osc)\n{{/OSC.length}}\n\n</nav>\n\n\n## General notes\n\nThis document lists xterm.js' support of terminal sequences. The sequences are grouped by their sequence type:\n\n- C0: single byte command (7bit control codes, byte range \\`\\\\x00\\` .. \\`\\\\x1F\\`, \\`\\\\x7F\\`)\n- C1: single byte command (8bit control codes, byte range \\`\\\\x80\\` .. \\`\\\\x9F\\`)\n- ESC: sequence starting with \\`ESC\\` (\\`\\\\x1B\\`)\n- CSI - Control Sequence Introducer: sequence starting with \\`ESC [\\` (7bit) or CSI (\\`\\\\x9B\\`, 8bit)\n- DCS - Device Control String: sequence starting with \\`ESC P\\` (7bit) or DCS (\\`\\\\x90\\`, 8bit)\n- OSC - Operating System Command: sequence starting with \\`ESC ]\\` (7bit) or OSC (\\`\\\\x9D\\`, 8bit)\n\nApplication Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported,\nany sequence of these types will be silently ignored. They are also not hookable by the API.\n\nNote that the list only marks sequences implemented in xterm.js' core codebase as supported. Missing sequences are either\nnot supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences\n(denoted as \"External\" where known).\n\nTo denote the sequences the tables use the same abbreviations as xterm does:\n- \\`Ps\\`: A single (usually optional) numeric parameter, composed of one or more decimal digits.\n- \\`Pm\\`: Multiple numeric parameters composed of any number of single numeric parameters, separated by ; character(s),\n  e.g. \\` Ps ; Ps ; ... \\`.\n- \\`Pt\\`: A text parameter composed of printable characters. Note that for most commands with \\`Pt\\` only\n  ASCII printables are specified to work. Additionally the parser will let pass any codepoint greater than C1 as printable.\n\n\n\n{{#C0.length}}\n## C0\n\n| Mnemonic | Name | Sequence | Short Description | Support |\n| -------- | ---- | -------- | ----------------- | ------- |\n{{#C0}}\n| {{mnemonic}} | {{name}} | \\`{{sequence}}\\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |\n{{/C0}}\n\n{{#C0.hasLongDescriptions}}\n{{#C0}}\n{{#longDescription.length}}\n<section class=\"sequence-details\">\n\n### {{name}}\n{{#longDescription}}\n{{{.}}}\n{{/longDescription}}\n\n</section>\n{{/longDescription.length}}\n{{/C0}}\n{{/C0.hasLongDescriptions}}\n\n{{/C0.length}}\n\n\n{{#C1.length}}\n## C1\n\n| Mnemonic | Name | Sequence | Short Description | Support |\n| -------- | ---- | -------- | ----------------- | ------- |\n{{#C1}}\n| {{mnemonic}} | {{name}} | \\`{{sequence}}\\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |\n{{/C1}}\n\n{{#C1.hasLongDescriptions}}\n{{#C1}}\n{{#longDescription.length}}\n<section class=\"sequence-details\">\n\n### {{name}}\n{{#longDescription}}\n{{{.}}}\n{{/longDescription}}\n\n</section>\n{{/longDescription.length}}\n{{/C1}}\n{{/C1.hasLongDescriptions}}\n\n{{/C1.length}}\n\n\n{{#CSI.length}}\n## CSI\n\n| Mnemonic | Name | Sequence | Short Description | Support |\n| -------- | ---- | -------- | ----------------- | ------- |\n{{#CSI}}\n| {{mnemonic}} | {{name}} | \\`\\`{{{sequence}}}\\`\\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |\n{{/CSI}}\n\n{{#CSI.hasLongDescriptions}}\n{{#CSI}}\n{{#longDescription.length}}\n<section class=\"sequence-details\">\n\n### {{name}}\n{{#longDescription}}\n{{{.}}}\n{{/longDescription}}\n\n</section>\n{{/longDescription.length}}\n{{/CSI}}\n{{/CSI.hasLongDescriptions}}\n\n{{/CSI.length}}\n\n\n{{#DCS.length}}\n## DCS\n\n| Mnemonic | Name | Sequence | Short Description | Support |\n| -------- | ---- | -------- | ----------------- | ------- |\n{{#DCS}}\n| {{mnemonic}} | {{name}} | \\`{{sequence}}\\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |\n{{/DCS}}\n\n{{#DCS.hasLongDescriptions}}\n{{#DCS}}\n{{#longDescription.length}}\n<section class=\"sequence-details\">\n\n### {{name}}\n{{#longDescription}}\n{{{.}}}\n{{/longDescription}}\n\n</section>\n{{/longDescription.length}}\n{{/DCS}}\n{{/DCS.hasLongDescriptions}}\n\n{{/DCS.length}}\n\n\n{{#ESC.length}}\n## ESC\n\n| Mnemonic | Name | Sequence | Short Description | Support |\n| -------- | ---- | -------- | ----------------- | ------- |\n{{#ESC}}\n| {{mnemonic}} | {{name}} | \\`{{sequence}}\\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |\n{{/ESC}}\n\n{{#ESC.hasLongDescriptions}}\n{{#ESC}}\n{{#longDescription.length}}\n<section class=\"sequence-details\">\n\n### {{name}}\n{{#longDescription}}\n{{{.}}}\n{{/longDescription}}\n\n</section>\n{{/longDescription.length}}\n{{/ESC}}\n{{/ESC.hasLongDescriptions}}\n\n{{/ESC.length}}\n\n\n{{#OSC.length}}\n## OSC\n\n**Note**: Other than listed in the table, the parser recognizes both ST (ECMA-48) and BEL (xterm) as OSC sequence finalizer.\n\n| Identifier | Sequence | Short Description | Support |\n| ---------- | -------- | ----------------- | ------- |\n{{#OSC}}\n| {{mnemonic}} | \\`{{sequence}}\\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |\n{{/OSC}}\n\n{{#OSC.hasLongDescriptions}}\n{{#OSC}}\n{{#longDescription.length}}\n<section class=\"sequence-details\">\n\n### {{name}}\n{{#longDescription}}\n{{{.}}}\n{{/longDescription}}\n\n</section>\n{{/longDescription.length}}\n{{/OSC}}\n{{/OSC.hasLongDescriptions}}\n\n{{/OSC.length}}\n\n<script type=\"text/javascript\">\n  const linkStates = {};\n\n  function hideDetailSections() {\n    for (let section of document.getElementsByClassName('sequence-details')) section.style.display = 'none';\n  }\n\n  function decorateDetailLinks() {\n    for (let link of document.getElementsByClassName('link-details')) {\n      link.addEventListener(\"click\", toggleDetails, false);\n      if (linkStates[link.href] === undefined) {\n        linkStates[link.href] = {open: false, links: [], trElem: null};\n      }\n      linkStates[link.href].links.push(link);\n    }\n  }\n\n  function openDetails(link) {\n    const closestTr = link.closest('tr');\n    if (!closestTr || !closestTr.parentNode) return;\n    const closestSection = document.getElementById(link.href.split('#')[1]).closest('section');\n    if (!closestSection || !closestSection.innerHTML) return;\n    const content = closestSection.innerHTML.split('</h3>')[1];\n\n    const newRow = closestTr.parentNode.insertRow(closestTr.rowIndex);\n    const cell = newRow.insertCell(0);\n    cell.innerHTML = '<div style=\"font-style: italic\">' + content + '</div>';\n    cell.colSpan = closestTr.children.length;\n    const state = linkStates[link.href];\n    state.trElem = newRow;\n    state.open = true;\n    if (link.innerHTML === 'more') link.innerHTML = 'less';\n  }\n\n  function closeDetails(link) {\n    const state = linkStates[link.href];\n    if (state.trElem && state.trElem.rowIndex !== -1) {\n      if (state.trElem.parentNode) state.trElem.parentNode.deleteRow(state.trElem.rowIndex-1); // why -1 here?\n      state.trElem.remove();\n      state.trElem = null;\n    }\n    state.open = false;\n    if (link.innerHTML === 'less') link.innerHTML = 'more';\n  }\n\n  function toggleDetails(ev) {\n    if (!ev.target || !ev.target.href) return;\n    const isOpen = linkStates[ev.target.href].open;\n    if (isOpen === undefined) return;\n    if (isOpen) closeDetails(ev.target);\n    else openDetails(ev.target);\n    ev.preventDefault();\n    return false;\n  }\n\n  function load() {\n    hideDetailSections();\n    decorateDetailLinks();\n  }\n  load(); // we are inlined behind all needed data, thus dont wait for onload (avoids flickering)\n</script>\n`\n\n// support status marcos\n// applied for:\n// - status field of single @vt line\n// - all lines in long description\nconst MACRO = [\n  // #Y - supported\n  [/#Y/g, s => '<span title=\"supported\">✓</span>'],\n  // #N - unsupported\n  [/#N/g, s => '<span title=\"unsupported\">✗</span>'],\n  // #P[reason] - partial support with a reason as title\n  [/#P\\[(.*?)\\]/g, (s, p1) => `<span title=\"${p1}\" style=\"text-decoration: underline\">Partial</span>`],\n  // #B[reason] - supported but broken in a certain way, reason in title\n  [/#B\\[(.*?)\\]/g, (s, p1) => `<span title=\"${p1}\" style=\"text-decoration: underline\">Broken</span>`],\n  // #E[notes] - support via external resource, eg. addon\n  [/#E\\[(.*?)\\]/g, (s, p1) => `<span title=\"${p1}\" style=\"text-decoration: underline\">External</span>`]\n];\n\nfunction applyMacros(s) {\n  for (let i = 0; i < MACRO.length; ++i) {\n    s = s.replace(MACRO[i][0], MACRO[i][1]);\n  }\n  return s;\n}\n\n// function replaceStatus(s) {\n//   if (s === 'supported') return '<span title=\"supported\">✓</span>';\n//   if (s === 'unsupported') return '<span title=\"unsupported\">✗</span>';\n//   return s;\n// }\n\nfunction createAnchorSlug(s) {\n  return s.toLowerCase().split(' ').join('-');\n}\n\nfunction empty(ar) {\n  return !ar.filter(Boolean, ar).length;\n}\n\nfunction* parseMultiLineGen(filename, s) {\n  if (!s.includes('@vt:')) {\n    return;\n  }\n  const lines = s.split('\\n').map(el => el.trim().replace(/\\*/, '').replace(/\\s/, ''));\n  let grabLine = false;\n  let longDescription = [];\n  let feature = undefined;\n  let noLineCount = 0;\n  for (const line of lines) {\n    if (grabLine) {\n      if (!line) noLineCount++;\n      if (noLineCount >= 2) {\n        if (feature) {\n          feature.longDescription = empty(longDescription) ? [] : longDescription;\n          feature.longTarget = createAnchorSlug(feature.name);\n          yield feature;\n        }\n        grabLine = false;\n        longDescription = [];\n        feature = undefined;\n        noLineCount = 0;\n      }\n      else if (line.indexOf('@vt:') === 0) {\n        if (feature) {\n          feature.longDescription = empty(longDescription) ? [] : longDescription;\n          feature.longTarget = createAnchorSlug(feature.name);\n          yield feature;\n        }\n        grabLine = true;\n        longDescription = [];\n        feature = undefined;\n        noLineCount = 0;\n      } else {\n        //longDescription.push(line);\n        longDescription.push(applyMacros(line));\n        if (line) noLineCount = 0;\n      }\n    }\n    if (line.indexOf('@vt:') === 0) {\n      feature = parseSingleLine(filename, line);\n      grabLine = true;\n    }\n  }\n  if (grabLine && feature) {\n    feature.longDescription = empty(longDescription) ? [] : longDescription;\n    feature.longTarget = createAnchorSlug(feature.name);\n    yield feature;\n  }\n}\n\nfunction parseSingleLine(filename, s) {\n  const line = s.trim();\n  const match = line.match(REX_VT_LINE);\n  if (match !== null) {\n    if (!TYPES.includes(match[2])) {\n      throw new Error(`unkown vt-command type \"${match[2]}\" specified in \"${filename}\"`);\n    }\n    return {\n      status: match[1],\n      type: match[2],\n      mnemonic: match[3],\n      name: match[4],\n      sequence: match[5],\n      shortDescription: match[6],\n      longDescription: [],\n      longTarget: '',\n      source: filename\n    };\n  }\n}\n\nfunction getSorter(entry) {\n  switch (entry) {\n    case 'C0':\n    case 'C1':\n      // NOTE: expects hex value notation at last position in sequence\n      return (a, b) => parseInt(a.sequence.slice(-2), 16) - parseInt(b.sequence.slice(-2), 16);\n    case 'OSC':\n      // NOTE: expects the decimal function identifier in mnemonic\n      return (a, b) => parseInt(a.mnemonic) - parseInt(b.mnemonic);\n    case 'DCS':\n      return (a, b) => a.mnemonic > b.mnemonic;\n    case 'CSI':\n    case 'ESC':\n      // default sort order by final byte\n      return (a, b) => {\n        // ugly hack to fix sorting of workaround in HPA sequence \"CSI Ps ` \"\n        // for HPA compare with length - 2 instead\n        const HPA = 'CSI Ps ` ';\n        const aa = a.sequence === HPA ? a.sequence.slice(0, -1) : a.sequence;\n        const bb = b.sequence === HPA ? b.sequence.slice(0, -1) : b.sequence;\n        return aa.charCodeAt(aa.length - 1) - bb.charCodeAt(bb.length - 1);\n      };\n    default:\n      return (a, b) => a.sequence > b.sequence;\n  }\n};\n\nfunction postProcessData(features) {\n  const featureTable = {};\n  for (const feature of features) {\n    // feature.status = replaceStatus(feature.status);\n    feature.status = applyMacros(feature.status);\n    if (featureTable[feature.type] === undefined) {\n      featureTable[feature.type] = [];\n    }\n    featureTable[feature.type].push(feature);\n    if (feature.longDescription.length) {\n      featureTable[feature.type].hasLongDescriptions = true;\n    }\n  }\n  for (const entry in featureTable) {\n    featureTable[entry].sort(getSorter(entry));\n  }\n  // console.error(featureTable);\n  featureTable.version = require('../package.json').version;\n  console.log(Mustache.render(MARKDOWN_TMPL, featureTable));\n}\n\nfunction main(filenames) {\n  // console.error(filenames);\n  let leftToProcess = filenames.length;\n  const features = [];\n  for (const filename of filenames) {\n    fs.readFile(filename, 'utf-8', (err, data) => {\n      let match;\n      while ((match = REX_COMMENTS.exec(data)) !== null) {\n        if (match.index === REX_COMMENTS.lastIndex) {\n          REX_COMMENTS.lastIndex++;\n        }\n        if (match[1]) {\n          for (let feature of parseMultiLineGen(filename, match[1])) {\n            if (feature) features.push(feature);\n          }\n        } else {\n          const feature = parseSingleLine(filename, match[2]);\n          if (feature) features.push(feature);\n        }\n      }\n      leftToProcess--;\n      if (!leftToProcess) {\n        postProcessData(features);\n      }\n    });\n  }\n}\n\nmain(process.argv.slice(2))\n"
  },
  {
    "path": "bin/lint_changes.js",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// @ts-check\n\nconst { execSync, spawn } = require('child_process');\nconst path = require('path');\n\nconst extensions = ['.ts', '.mts'];\nconst fix = process.argv.includes('--fix');\n\n// Get uncommitted changed files (staged + unstaged)\nfunction getChangedFiles() {\n  try {\n    const output = execSync('git diff --name-only --diff-filter=ACMR HEAD', {\n      encoding: 'utf-8',\n      cwd: path.join(__dirname, '..')\n    });\n    return output.split('\\n').filter(f => f && extensions.some(ext => f.endsWith(ext)));\n  } catch {\n    return [];\n  }\n}\n\nconst files = getChangedFiles();\n\nif (files.length === 0) {\n  console.log('No changed TypeScript files to lint.');\n  process.exit(0);\n}\n\nconsole.log(`Linting ${files.length} changed file(s)...`);\n\nconst eslintArgs = ['--max-warnings', '0', '--no-warn-ignored'];\nif (fix) {\n  eslintArgs.push('--fix');\n}\neslintArgs.push(...files);\n\nconst eslint = process.platform === 'win32' ? 'eslint.cmd' : 'eslint';\nconst child = spawn(eslint, eslintArgs, {\n  stdio: 'inherit',\n  cwd: path.join(__dirname, '..'),\n  shell: process.platform === 'win32'\n});\n\nchild.on('close', code => process.exit(code ?? 0));\n"
  },
  {
    "path": "bin/package_headless.js",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst { exec } = require('child_process');\nconst fs = require('fs');\nconst { join } = require('path');\n\nconst repoRoot = join(__dirname, '..');\nconst headlessRoot = join(repoRoot, 'headless');\n\nconsole.log('> headless/package.json');\nconst xtermPackageJson = require('../package.json');\nconst xtermHeadlessPackageJson = {\n  ...xtermPackageJson,\n  name: '@xterm/headless',\n  description: 'A headless terminal component that runs in Node.js',\n  main: 'lib-headless/xterm-headless.js',\n  types: 'typings/xterm-headless.d.ts',\n};\ndelete xtermHeadlessPackageJson['scripts'];\ndelete xtermHeadlessPackageJson['devDependencies'];\ndelete xtermHeadlessPackageJson['style'];\nfs.writeFileSync(join(headlessRoot, 'package.json'), JSON.stringify(xtermHeadlessPackageJson, null, 1));\nconsole.log(fs.readFileSync(join(headlessRoot, 'package.json')).toString());\n\nconsole.log('> headless/typings/');\nmkdirF(join(headlessRoot, 'typings'));\nfs.copyFileSync(\n  join(repoRoot, 'typings/xterm-headless.d.ts'),\n  join(headlessRoot, 'typings/xterm-headless.d.ts')\n);\n\nconsole.log('> headless/logo-full.png');\nfs.copyFileSync(\n  join(repoRoot, 'images/logo-full.png'),\n  join(headlessRoot, 'logo-full.png')\n);\n\nfunction mkdirF(p) {\n  if (!fs.existsSync(p)) {\n    fs.mkdirSync(p);\n  }\n}\n\nconsole.log('> Publish dry run');\nexec('npm publish --dry-run', { cwd: headlessRoot }, (error, stdout, stderr) => {\n  if (error) {\n    console.log(`error: ${error.message}`);\n    return;\n  }\n  if (stderr) {\n      console.error(`stderr:\\n${stderr}`);\n  }\n  console.log(`stdout:\\n${stdout}`);\n});\n"
  },
  {
    "path": "bin/publish.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst cp = require('child_process');\nconst fs = require('fs');\nconst os = require('os');\nconst path = require('path');\n\n// Setup auth\nif (process.env['NPM_AUTH_TOKEN']) {\n  fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`);\n}\n\nlog('Configuration:', 'green')\nconst isDryRun = process.argv.includes('--dry');\nif (isDryRun) {\n  log('  Publish dry run');\n}\n\nconst allAddons = process.argv.includes('--all-addons');\nif (allAddons) {\n  log('  Publish all addons');\n}\n\nconst repoCommit = getRepoCommit();\nconst changedFiles = getChangedFilesInCommit('HEAD');\n\n// Always publish xterm, technically this isn't needed if\n// `changedFiles.some(e => e.search(/^addons\\//)`, but it's here for convenience to get the right\n// peer dependencies for addons.\nconst result = checkAndPublishPackage(path.resolve(__dirname, '..'), repoCommit, undefined, true);\nconst isStableRelease = result.isStableRelease;\nconst peerDependencies = result.nextVersion.includes('beta') ? {\n  '@xterm/xterm': `^${result.nextVersion}`,\n} : undefined;\ncheckAndPublishPackage(path.resolve(__dirname, '../headless'), repoCommit);\n\n// Addon peer dependencies\n\n// Publish addons if any files were changed inside of the addon\nconst addonPackageDirs = [\n  path.resolve(__dirname, '../addons/addon-attach'),\n  path.resolve(__dirname, '../addons/addon-clipboard'),\n  path.resolve(__dirname, '../addons/addon-fit'),\n  path.resolve(__dirname, '../addons/addon-image'),\n  path.resolve(__dirname, '../addons/addon-ligatures'),\n  path.resolve(__dirname, '../addons/addon-progress'),\n  path.resolve(__dirname, '../addons/addon-search'),\n  path.resolve(__dirname, '../addons/addon-serialize'),\n  path.resolve(__dirname, '../addons/addon-unicode11'),\n  path.resolve(__dirname, '../addons/addon-unicode-graphemes'),\n  path.resolve(__dirname, '../addons/addon-web-fonts'),\n  path.resolve(__dirname, '../addons/addon-web-links'),\n  path.resolve(__dirname, '../addons/addon-webgl')\n];\nlog(`Checking if addons need to be published`, 'green');\nfor (const p of addonPackageDirs) {\n  const addon = path.basename(p);\n  if (allAddons || changedFiles.some(e => e.includes(addon))) {\n    checkAndPublishPackage(p, repoCommit, peerDependencies);\n  }\n}\n\n// Publish website if it's a stable release\nif (isStableRelease) {\n  updateWebsite();\n}\n\nfunction checkAndPublishPackage(packageDir, repoCommit, peerDependencies, updateVersionTs = false) {\n  const packageJson = require(path.join(packageDir, 'package.json'));\n\n  // Determine if this is a stable or beta release\n  const publishedVersions = getPublishedVersions(packageJson);\n  const isStableRelease = !publishedVersions.includes(packageJson.version);\n\n  // Get the next version\n  let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson);\n  log(`Publishing version: ${nextVersion}`, 'green');\n\n  // Update Version.ts with the new version\n  if (updateVersionTs) {\n    const versionTsPath = path.join(packageDir, 'src/common/Version.ts');\n    const versionTsContent = fs.readFileSync(versionTsPath, 'utf8');\n    const updatedVersionTs = versionTsContent.replace(\n      /export const XTERM_VERSION = '[^']+';/,\n      `export const XTERM_VERSION = '${nextVersion}';`\n    );\n    fs.writeFileSync(versionTsPath, updatedVersionTs);\n    log(`Updated ${versionTsPath} to version ${nextVersion}`);\n  }\n\n  // Set the version in package.json\n  const packageJsonFile = path.join(packageDir, 'package.json');\n  packageJson.version = nextVersion;\n\n  // Set the commit in package.json\n  if (repoCommit) {\n    packageJson.commit = repoCommit;\n    log(`Set commit of ${packageJsonFile} to ${repoCommit}`);\n  } else {\n    throw new Error(`No commit set`);\n  }\n\n  // Set peer dependencies\n  if (peerDependencies) {\n    packageJson.peerDependencies = peerDependencies;\n    log(`Set peerDependencies of ${packageJsonFile} to ${JSON.stringify(peerDependencies)}`);\n  } else {\n    log(`Skipping peerDependencies`);\n  }\n\n  // Write new package.json\n  fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));\n\n  // Publish\n  const args = ['publish', '--access', 'public'];\n  if (!isStableRelease) {\n    args.push('--tag', 'beta');\n  }\n  if (isDryRun) {\n    args.push('--dry-run');\n  }\n  log(`Spawn: npm ${args.join(' ')}`);\n  const result = cp.spawnSync('npm', args, {\n    cwd: packageDir,\n    stdio: 'inherit'\n  });\n  if (result.status) {\n    throw new Error(`Spawn exited with code ${result.status}`);\n  }\n\n  return { isStableRelease, nextVersion };\n}\n\nfunction getRepoCommit() {\n  const commitProcess = cp.spawnSync('git', ['log', '-1', '--format=\"%H\"']);\n  if (commitProcess.stdout.length === 0 && commitProcess.stderr) {\n    const err = versionsProcess.stderr.toString();\n    throw new Error('Could not get repo commit\\n' + err);\n  }\n  const output = commitProcess.stdout.toString().trim();\n  return output.replace(/^\"/, '').replace(/\"$/, '');\n}\n\nfunction getNextBetaVersion(packageJson) {\n  if (!/^\\d+\\.\\d+\\.\\d+$/.exec(packageJson.version)) {\n    throw new Error('The package.json version must be of the form x.y.z');\n  }\n  const tag = 'beta';\n  const stableVersion = packageJson.version.split('.');\n  const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;\n  const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag);\n  if (publishedVersions.length === 0) {\n    return `${nextStableVersion}-${tag}.1`;\n  }\n  const latestPublishedVersion = publishedVersions.sort((a, b) => {\n    const aVersion = parseInt(a.slice(a.search(/\\d+$/)));\n    const bVersion = parseInt(b.slice(b.search(/\\d+$/)));\n    return aVersion > bVersion ? -1 : 1;\n  })[0];\n  const latestTagVersion = parseInt(latestPublishedVersion.slice(latestPublishedVersion.search(/\\d+$/)), 10);\n  return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;\n}\n\nfunction asArray(value) {\n  return Array.isArray(value) ? value : [value];\n}\n\nfunction getPublishedVersions(packageJson, version, tag) {\n  const versionsProcess = cp.spawnSync(\n    os.platform === 'win32' ? 'npm.cmd' : 'npm',\n    ['view', packageJson.name, 'versions', '--json'],\n    { shell: true }\n  );\n  if (versionsProcess.stdout.length === 0 && versionsProcess.stderr) {\n    const err = versionsProcess.stderr.toString();\n    if (err.indexOf('404 Not Found - GET https://registry.npmjs.org/@xterm') > 0) {\n      return [];\n    }\n    throw new Error('Could not get published versions\\n' + err);\n  }\n  const output = JSON.parse(versionsProcess.stdout);\n  if (typeof output === 'object' && !Array.isArray(output)) {\n    if (output.error?.code === 'E404')  {\n      return [];\n    }\n    throw new Error('Could not get published versions\\n' + output);\n  }\n  if (!output || Array.isArray(output) && output.length === 0) {\n    return [];\n  }\n  const versionsJson = asArray(output);\n  if (tag) {\n    return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}.[0-9]+`)));\n  }\n  return versionsJson;\n}\n\nfunction getChangedFilesInCommit(commit) {\n  const args = ['log', '-m', '-1', '--name-only', `--pretty=format:`, commit];\n  const result = cp.spawnSync('git', args);\n  const output = result.stdout.toString();\n  const changedFiles = output.split('\\n').filter(e => e.length > 0);\n  return changedFiles;\n}\n\nfunction updateWebsite() {\n  log('Updating website', 'green');\n  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'website-'));\n  const packageJson = require(path.join(path.resolve(__dirname, '..'), 'package.json'));\n  if (!isDryRun) {\n    cp.spawnSync('sh', [path.join(__dirname, 'update-website.sh'), packageJson.version], { cwd, stdio: [process.stdin, process.stdout, process.stderr] });\n  }\n}\n\n/**\n * @param {string} message\n */\nfunction log(message, color) {\n  let colorSequence = '';\n  switch (color) {\n    case 'green': {\n      colorSequence = '\\x1b[32m';\n      break;\n    }\n  }\n  console.info([\n    `[\\x1b[2m${new Date().toLocaleTimeString('en-GB')}\\x1b[0m] `,\n    colorSequence,\n    message,\n    '\\x1b[0m',\n  ].join(''));\n}\n"
  },
  {
    "path": "bin/test_integration.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// @ts-check\n\nconst cp = require('child_process');\nconst path = require('path');\n\nlet argv = process.argv.slice(2);\nlet suiteFilter = undefined;\nwhile (argv.some(e => e.startsWith('--suite='))) {\n  const i = argv.findIndex(e => e.startsWith('--suite='));\n  const match = argv[i].match(/--suite=(?<suitename>.+)/)\n  suiteFilter = match?.groups?.suitename ?? undefined;\n  argv.splice(i, 1);\n}\n\nlet configs = [\n  { name: 'core', path: 'out-esbuild-test/playwright/playwright.config.js' }\n];\nconst addons = [\n  'attach',\n  'clipboard',\n  'fit',\n  'image',\n  'progress',\n  'search',\n  'serialize',\n  'unicode-graphemes',\n  'unicode11',\n  'web-fonts',\n  'web-links',\n  'webgl',\n];\nfor (const addon of addons) {\n  configs.push({ name: `addon-${addon}`, path: `addons/addon-${addon}/out-esbuild-test/playwright.config.js` });\n}\n\nif (suiteFilter) {\n  configs = configs.filter(e => e.name === suiteFilter);\n}\n\nfunction npmBinScript(script) {\n  return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ?\n    `${script}.cmd` : script));\n}\n\nasync function run() {\n  for (const config of configs) {\n    const command = npmBinScript('playwright');\n    const args = ['test', '-c', config.path, ...argv];\n    console.log(`Running suite \\x1b[1;34m${config.name}...\\x1b[0m`);\n    console.log(`\\n\\x1b[32m${command}\\x1b[0m`, args);\n    const run = cp.spawnSync(command, args, {\n        cwd: path.resolve(__dirname, '..'),\n        shell: true,\n        stdio: 'inherit'\n      }\n    );\n\n    if (run.error) {\n      console.error(run.error);\n      process.exit(run.status ?? -1);\n    }\n\n    process.exit(run.status);\n  }\n}\nrun();\n"
  },
  {
    "path": "bin/test_mousemodes.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n * \n * Script to test different mouse modes in terminal emulators.\n * Tests for protocols DECSET 9, 1000, 1002, 1003 with different\n * report encodings (default, UTF8, SGR, URXVT, SGR-pixels).\n * \n * VT200 Highlight mode (DECSET 1001) is not implemented.\n * \n * The test basically applies the report data to the cursor, thus\n * a mouse report should move the cursor to the cell under the mouse.\n * Furthermore the reports are printed in the left lower corner as\n * raw data and their meaning.\n * \n * A failing test might show:\n *  - wrong coords: the cursor will jump to some other place\n *  - wrong buttons: see meaning output and check whether it makes sense\n *  - faulty reports: inspect the raw data and compare with other emulators\n *  - missing events: compare with spec / other emulators\n */\n\nlet activeProtocol = 0;\nlet activeEnc = 0;\n\nconst stdin = process.openStdin();\nprocess.stdin.setRawMode(true);\n\n// close handler - reset terminal on exit\nstdin.addListener('data', function(data) {\n  if (data[0] === 0x03) {\n      process.stdin.setRawMode(false);\n      process.stdout.write('\\x1bc');\n      process.exit();\n  }\n  if (data[0] === 0x01) {\n    switchActiveProtocol();\n    printMenu();\n  }\n  if (data[0] === 0x02) {\n    switchActiveEnc();\n    printMenu();\n  }\n  console.log('\\x1b[100;H\\x1b[2A\\x1b[2KReport:', data, [data.toString('binary')]);\n  // filter mouse reports\n  if (data[0] === 0x1b && data[1] === '['.charCodeAt(0)) {\n    applyReportData(data);\n  }\n});\n\n// button definitions\nconst buttons = {\n  '<none>':  -1,\n  left:       0,\n  middle:     1,\n  right:      2,\n  released:   3,\n  wheelUp:    4,\n  wheelDown:  5,\n  wheelLeft:  6,\n  wheelRight: 7,\n  aux1:       8,\n  aux2:       9,\n  aux3:      10,\n  aux4:      11,\n  aux5:      12,\n  aux6:      13,\n  aux7:      14,\n  aux8:      15\n};\nconst reverseButtons = {};\nfor (const el in buttons) {\n  reverseButtons[buttons[el]] = el;\n}\n\n// extract button data from buttonCode\nfunction evalButtonCode(code) {\n  // more than 15 buttons are not supported\n  if (code > 255) {\n    return {button: 'invalid', action: 'invalid', modifier: {}};\n  }\n  const modifier = {shift: !!(code & 4), meta: !!(code & 8), control: !!(code & 16)};\n  const move = code & 32;\n  let button = code & 3;\n  if (code & 128) {\n    button |= 8;\n  }\n  if (code & 64) {\n    button |= 4\n  }\n  let action = 'press';\n  let buttonS = reverseButtons[button];\n  if (button === 3) {\n    buttonS = '<none>';\n    action = 'release';\n  }\n  if (move) {\n    action = 'move';\n  } else if (4 <= button && button <= 7) {\n    buttonS = 'wheel';\n    action = button === 4 ? 'up' : button === 5 ? 'down' : button === 6 ? 'left' : 'right';\n  }\n  return {button: buttonS, action, modifier};\n}\n\n// protocols\nconst PROTOCOLS = {\n  '9    (X10: press only)': '\\x1b[?9h',\n  '1000 (VT200: press, release, wheel)': '\\x1b[?1000h',\n  // '1001 (VT200 highlight)': '\\x1b[?1001h',  // handle of backreport - not implemented\n  '1002 (press, release, move on pressed, wheel)': '\\x1b[?1002h',\n  '1003 (press, relase, move, wheel)': '\\x1b[?1003h'\n}\n\n// encodings: ENCODING_NAME => [sequence, parse_report]\nconst ENC = {\n  'DEFAULT'  : [\n    '',\n    // format: CSI M <button + 32> <row + 32> <col + 32>\n    report => ({\n      state: evalButtonCode(report[3] - 32),\n      col: report[4] - 32,\n      row: report[5] - 32\n    })\n  ],\n  'UTF8' : [\n    '\\x1b[?1005h',\n    // format: CSI M <button + 32> <row + 32> <col + 32>\n    //         + utf8 encoding on row/col\n    report => {\n      const sReport = report.toString(); // decode with utf8\n      return {\n        state: evalButtonCode(sReport.charCodeAt(3) - 32),\n        col: sReport.charCodeAt(4) - 32,\n        row: sReport.charCodeAt(5) - 32\n      };\n    }\n  ],\n  'SGR'  : [\n    '\\x1b[?1006h',\n    // format: CSI < Pbutton ; Prow ; Pcol M\n    report => {\n      // strip off introducer + M\n      const sReport = report.toString().slice(3, -1);\n      const [buttonCode, col, row] = sReport.split(';');\n      const state = evalButtonCode(buttonCode);\n      if (report[report.length - 1] === 'm'.charCodeAt(0)) {\n        state.action = 'release';\n      }\n      return {state, row, col};\n    }\n  ],\n  'URXVT': [\n    '\\x1b[?1015h',\n    // format: CSI <button + 32> ; Prow ; Pcol M\n    report => {\n      // strip off introducer + M\n      const sReport = report.toString().slice(2, -1);\n      const [button, col, row] = sReport.split(';');\n      return {state: evalButtonCode(button - 32), row, col};\n    }\n  ],\n  'SGR_PIXELS'  : [\n    '\\x1b[?1016h',\n    // format: CSI < Pbutton ; Prow ; Pcol M\n    report => {\n      // strip off introducer + M\n      const sReport = report.toString().slice(3, -1);\n      const [buttonCode, col, row] = sReport.split(';');\n      const state = evalButtonCode(buttonCode);\n      if (report[report.length - 1] === 'm'.charCodeAt(0)) {\n        state.action = 'release';\n      }\n      return {state, row, col};\n    }\n  ]\n}\n\nfunction printMenu() {\n  console.log('\\x1b[2J\\x1b\\[HTest mouse reports [Ctrl-C to exit]');\n  console.log();\n  console.log('  Selected protocol [Ctrl-A to switch]');\n  const protocols = Object.keys(PROTOCOLS);\n  for (let i = 0; i < protocols.length; ++i) {\n    console.log(`  ${activeProtocol === i ? '->' : '  '}  ${protocols[i]}`);\n  }\n  console.log();\n  console.log('  Selected encoding [Ctrl-B to switch]');\n  const encs = Object.keys(ENC);\n  for (let i = 0; i < encs.length; ++i) {\n    console.log(`  ${activeEnc === i ? '->' : '  '}  ${encs[i]}`);\n  }\n  process.stdout.write('\\x1b[100;H');\n}\n\nfunction switchActiveProtocol() {\n  activeProtocol++;\n  activeProtocol %= Object.keys(PROTOCOLS).length;\n  activate();\n}\n\nfunction switchActiveEnc() {\n  activeEnc++;\n  activeEnc %= Object.keys(ENC).length;\n  activate();\n}\n\nfunction activate() {\n  // clear all protocols and encodings\n  process.stdout.write('\\x1b[?9l\\x1b[?1000l\\x1b[?1001l\\x1b[?1002l\\x1b[?1003l');\n  process.stdout.write('\\x1b[?1005l\\x1b[?1006l\\x1b[?1015l');\n  // apply new protocol and encoding\n  process.stdout.write(PROTOCOLS[Object.keys(PROTOCOLS)[activeProtocol]]);\n  process.stdout.write(ENC[Object.keys(ENC)[activeEnc]][0]);\n  console.log('\\x1b[100;H\\x1b[2A\\x1b[2KReport:');\n}\n\nfunction applyReportData(data) {\n  let {state, row, col} = ENC[Object.keys(ENC)[activeEnc]][1](data);\n  if (Object.keys(ENC)[activeEnc] !== 'SGR_PIXELS') {\n    console.log('\\x1b[2KButton:', state.button, 'Action:', state.action, 'Modifier:', state.modifier, 'row:', row, 'col:', col);\n    process.stdout.write(`\\x1b[${row};${col}H`);\n  } else {\n    console.log('\\x1b[2KButton:', state.button, 'Action:', state.action, 'Modifier:', state.modifier, 'x:', row, 'y:', col);\n  }\n}\n\nprintMenu();\nactivate();\n"
  },
  {
    "path": "bin/test_unit.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst cp = require('child_process');\nconst path = require('path');\n\nconst COVERAGE_LINES_THRESHOLD = 40;\n\n// Add `out` to the NODE_PATH so absolute paths can be resolved.\nconst env = { ...process.env };\nenv.NODE_PATH = path.resolve(__dirname, '../out-esbuild');\n\nlet testFiles = ['**/out-esbuild/**/*.test.js'];\n\nlet flagArgs = [];\n\nif (process.argv.length > 2) {\n  const args = process.argv.slice(2);\n  flagArgs = args.filter(e => e.startsWith('--'));\n  // ability to inject particular test files via\n  // npm run test [testFileA testFileB ...]\n  files = args.filter(e => !e.startsWith('--'));\n  if (files.length) {\n    testFiles = files;\n  }\n}\n\nconst checkCoverage = flagArgs.indexOf('--coverage') >= 0;\nif (checkCoverage) {\n  flagArgs.splice(flagArgs.indexOf('--coverage'), 1);\n  const executable = npmBinScript('nyc');\n  const args = [\n    '--check-coverage',\n    `--lines=${COVERAGE_LINES_THRESHOLD}`,\n    '--exclude=out-esbuild/vs/**',\n    npmBinScript('mocha'),\n    ...testFiles,\n    ...flagArgs\n  ];\n  console.info('executable', executable);\n  console.info('args', args);\n  const run = cp.spawnSync(\n    executable,\n    args,\n    {\n      cwd: path.resolve(__dirname, '..'),\n      env,\n      shell: true,\n      stdio: 'inherit'\n    }\n  );\n  process.exit(run.status);\n}\n\nconst run = cp.spawnSync(\n  npmBinScript('mocha'),\n  [...testFiles, ...flagArgs],\n  {\n    cwd: path.resolve(__dirname, '..'),\n    env,\n    shell: true,\n    stdio: 'inherit'\n  }\n);\n\nfunction npmBinScript(script) {\n  return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));\n}\n\nif (run.error) {\n  console.error(run.error);\n}\nprocess.exit(run.status ?? -1);\n"
  },
  {
    "path": "bin/update-website.sh",
    "content": "#!/bin/sh\n\n# Name the arguments\nVERSION=$1\n\n# Clone docs repo and update the documentation\ngit clone https://github.com/xtermjs/xtermjs.org\ncd xtermjs.org\nnpm ci\n./bin/update-docs\n\n# Add changes to index and only proceed if there are changes to commit\ntouch test-file\ngit add .\nif ! git diff-index --quiet HEAD --; then\n\n  # Delete the upstream branch if it exists for some reason\n  export BRANCH_NAME=update-$VERSION\n  git branch -D $BRANCH_NAME || true\n  git push origin :$BRANCH_NAME || true\n\n  # Create commit and push it to update-x.y.z\n  git checkout -b $BRANCH_NAME\n  git config --global user.name Daniel Imms\n  git config --global user.email tyriar@tyriar.com\n  git commit -m 'Update docs for v$VERSION'\n  git push --set-upstream origin update-4.2.0\n  git push -f\n\n  # Create a PR in the GitHub repo\n  curl \\\n    -H \"Authorization: token $GITHUB_TOKEN\" \\\n    -X POST \\\n    -d \"{\\\"title\\\":\\\"Update docs for v$VERSION\\\",\\\"base\\\":\\\"master\\\",\\\"head\\\":\\\"xtermjs:$BRANCH_NAME\\\"}\" \\\n    https://api.github.com/repos/xtermjs/xtermjs.org/pulls\n\nelse\n\n  echo \"No changes to commit\"\n\nfi\n"
  },
  {
    "path": "bin/vs_base_find_unused.js",
    "content": "// @ts-check\n\nconst { dirname } = require(\"path\");\nconst ts = require(\"typescript\");\nconst fs = require(\"fs\");\n\nfunction findUnusedSymbols(\n  /** @type string */ tsconfigPath\n) {\n  // Initialize a program using the project's tsconfig.json\n  const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n  const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(tsconfigPath));\n\n  // Initialize a program with the parsed configuration\n  const program = ts.createProgram(parsedConfig.fileNames, {\n    ...parsedConfig.options,\n    noUnusedLocals: true\n  });\n  const sourceFiles = program.getSourceFiles();\n  const usedBaseSourceFiles = sourceFiles.filter(e => e.fileName.includes('src/vs/base/'));\n  const usedFilesInBase = usedBaseSourceFiles.map(e => e.fileName.replace(/^.+\\/src\\//, 'src/')).sort((a, b) => a.localeCompare(b));\n  // console.log('Source files used in src/vs/base/:', used);\n\n  // Get an array of all files that exist in src/vs/base/\n  const allFilesInBase = (\n    fs.readdirSync('src/vs/base', { recursive: true, withFileTypes: true })\n      .filter(e => e.isFile())\n      // @ts-ignore HACK: This is only available in Node 20\n      .map(e => `${e.parentPath}/${e.name}`.replace(/\\\\/g, '/'))\n  );\n  const unusedFilesInBase = allFilesInBase.filter(e => !usedFilesInBase.includes(e));\n\n  console.log({\n    allFilesInBase,\n    usedFilesInBase,\n    unusedFilesInBase\n  });\n}\n\n// Example usage\nfindUnusedSymbols(\"./src/browser/tsconfig.json\");\n"
  },
  {
    "path": "bin/vs_base_update.ps1",
    "content": "# Get latest vscode repo\nif (Test-Path -Path \"src/vs/temp\") {\n  Write-Host \"`e[32m> Fetching latest`e[0m\"\n  git -C src/vs/temp checkout\n  git -C src/vs/temp pull\n} else {\n  Write-Host \"`e[32m> Cloning microsoft/vscode`e[0m\"\n  $null = New-Item -ItemType Directory -Path \"src/vs/temp\" -Force\n  git clone https://github.com/microsoft/vscode src/vs/temp\n}\n\n# Delete old base\nWrite-Host \"`e[32m> Deleting old base`e[0m\"\n$null = Remove-Item -Recurse -Force \"src/vs/base\"\n\n# Copy base\nWrite-Host \"`e[32m> Copying base`e[0m\"\nCopy-Item -Path \"src/vs/temp/src/vs/base\" -Destination \"src/vs/base\" -Recurse\n\n# Comment out any CSS imports\nWrite-Host \"`e[32m> Commenting out CSS imports\" -NoNewline\n$baseFiles = Get-ChildItem -Path \"src/vs/base\" -Recurse -File\n$count = 0\nforeach ($file in $baseFiles) {\n  $content = Get-Content -Path $file.FullName\n  $updatedContent = $content | ForEach-Object {\n    if ($_ -match \"^import 'vs/css!\") {\n      Write-Host \"`e[32m.\" -NoNewline\n      $count++\n      \"// $_\"\n    } else {\n      $_\n    }\n  }\n  $updatedContent | Set-Content -Path $file.FullName\n}\nWrite-Host \" $count files patched`e[0m\"\n\n# Replace `monaco-*` with `xterm-*`, this will help avoid any styling conflicts when monaco and\n# xterm.js are used in the same project.\nWrite-Host \"`e[32m> Replacing monaco-* class names with xterm-* `e[0m\" -NoNewline\n$baseFiles = Get-ChildItem -Path \"src/vs/base\" -Recurse -File\n$count = 0\nforeach ($file in $baseFiles) {\n  $content = Get-Content -Path $file.FullName\n  if ($content -match \"monaco-([a-zA-Z\\-]+)\") {\n    $updatedContent = $content -replace \"monaco-([a-zA-Z\\-]+)\", 'xterm-$1'\n    Write-Host \"`e[32m.\" -NoNewline\n    $count++\n    $updatedContent | Set-Content -Path $file.FullName\n  }\n}\nWrite-Host \" $count files patched`e[0m\"\n\n# Copy typings\nWrite-Host \"`e[32m> Copying typings`e[0m\"\nCopy-Item -Path \"src/vs/temp/src/typings\" -Destination \"src/vs\" -Recurse -Force\n\n# Deleting unwanted typings\nWrite-Host \"`e[32m> Deleting unwanted typings`e[0m\"\n$null = Remove-Item -Path \"src/vs/typings/vscode-globals-modules.d.ts\" -Force\n"
  },
  {
    "path": "css/xterm.css",
    "content": "/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n *   Fabrice Bellard's javascript vt100 for jslinux:\n *   http://bellard.org/jslinux/\n *   Copyright (c) 2011 Fabrice Bellard\n *   The original design remains. The terminal itself\n *   has been extended to include xterm CSI codes, among\n *   other features.\n */\n\n/**\n *  Default styles for xterm.js\n */\n\n.xterm {\n    cursor: text;\n    position: relative;\n    user-select: none;\n    -ms-user-select: none;\n    -webkit-user-select: none;\n}\n\n.xterm.focus,\n.xterm:focus {\n    outline: none;\n}\n\n.xterm .xterm-helpers {\n    position: absolute;\n    top: 0;\n    /**\n     * The z-index of the helpers must be higher than the canvases in order for\n     * IMEs to appear on top.\n     */\n    z-index: 5;\n}\n\n.xterm .xterm-helper-textarea {\n    padding: 0;\n    border: 0;\n    margin: 0;\n    /* Move textarea out of the screen to the far left, so that the cursor is not visible */\n    position: absolute;\n    opacity: 0;\n    left: -9999em;\n    top: 0;\n    width: 0;\n    height: 0;\n    z-index: -5;\n    /** Prevent wrapping so the IME appears against the textarea at the correct position */\n    white-space: nowrap;\n    overflow: hidden;\n    resize: none;\n}\n\n.xterm .composition-view {\n    /* TODO: Composition position got messed up somewhere */\n    background: #000;\n    color: #FFF;\n    display: none;\n    position: absolute;\n    white-space: nowrap;\n    z-index: 1;\n}\n\n.xterm .composition-view.active {\n    display: block;\n}\n\n.xterm .xterm-viewport {\n    overflow-y: scroll;\n    cursor: default;\n    position: absolute;\n    right: 0;\n    left: 0;\n    top: 0;\n    bottom: 0;\n}\n\n.xterm:not(.allow-transparency) .xterm-viewport {\n    /* On OS X this is required in order for the scroll bar to appear fully opaque */\n    background-color: #000;\n}\n\n.xterm .xterm-screen {\n    position: relative;\n}\n\n.xterm .xterm-screen canvas {\n    position: absolute;\n    left: 0;\n    top: 0;\n}\n\n.xterm.enable-mouse-events {\n    /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */\n    cursor: default;\n}\n\n.xterm.xterm-cursor-pointer,\n.xterm .xterm-cursor-pointer {\n    cursor: pointer;\n}\n\n.xterm.column-select.focus {\n    /* Column selection mode */\n    cursor: crosshair;\n}\n\n.xterm .xterm-accessibility:not(.debug),\n.xterm .xterm-message {\n    position: absolute;\n    left: 0;\n    top: 0;\n    bottom: 0;\n    right: 0;\n    z-index: 10;\n    color: transparent;\n    pointer-events: none;\n}\n\n.xterm .xterm-accessibility-tree:not(.debug) *::selection {\n  color: transparent;\n}\n\n.xterm .xterm-accessibility-tree {\n  font-family: monospace;\n  user-select: text;\n  white-space: pre;\n}\n\n.xterm .xterm-accessibility-tree > div {\n  transform-origin: left;\n  width: fit-content;\n}\n\n.xterm .live-region {\n    position: absolute;\n    left: -9999px;\n    width: 1px;\n    height: 1px;\n    overflow: hidden;\n}\n\n.xterm-dim {\n    /* Dim should not apply to background, so the opacity of the foreground color is applied\n     * explicitly in the generated class and reset to 1 here */\n    opacity: 1 !important;\n}\n\n.xterm-underline-1 { text-decoration: underline; }\n.xterm-underline-2 { text-decoration: double underline; }\n.xterm-underline-3 { text-decoration: wavy underline; }\n.xterm-underline-4 { text-decoration: dotted underline; }\n.xterm-underline-5 { text-decoration: dashed underline; }\n\n.xterm-overline {\n    text-decoration: overline;\n}\n\n.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }\n.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }\n.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }\n.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }\n.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }\n\n.xterm-strikethrough {\n    text-decoration: line-through;\n}\n\n.xterm-screen .xterm-decoration-container .xterm-decoration {\n\tz-index: 6;\n\tposition: absolute;\n}\n\n.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {\n\tz-index: 7;\n}\n\n.xterm-decoration-overview-ruler {\n    z-index: 8;\n    position: absolute;\n    top: 0;\n    right: 0;\n    pointer-events: none;\n}\n\n.xterm-decoration-top {\n    z-index: 2;\n    position: relative;\n}\n\n\n\n/* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */\n\n/* xterm.js customization: Override xterm's cursor style */\n.xterm .xterm-scrollable-element > .xterm-scrollbar {\n    cursor: default;\n}\n\n/* Arrows */\n\n.xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-scra {\n\tcursor: pointer;\n    background-color: var(--vscode-scrollbarSliderBackground, rgba(100, 100, 100, 0.4));\n    mask-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 11'><path d='M2.5 8.5 L5.5 2.5 L8.5 8.5 Z' fill='black'/></svg>\");\n    -webkit-mask-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 11'><path d='M2.5 8.5 L5.5 2.5 L8.5 8.5 Z' fill='black'/></svg>\");\n    mask-repeat: no-repeat;\n    -webkit-mask-repeat: no-repeat;\n    mask-position: center center;\n    -webkit-mask-position: center center;\n    mask-size: 100% 100%;\n    -webkit-mask-size: 100% 100%;\n}\n\n.xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-scra.xterm-arrow-down {\n    transform: rotate(180deg);\n}\n\n.xterm .xterm-scrollable-element > .xterm-visible {\n\topacity: 1;\n\n\t/* Background rule added for IE9 - to allow clicks on dom node */\n\tbackground:rgba(0,0,0,0);\n\n\ttransition: opacity 100ms linear;\n\t/* In front of peek view */\n\tz-index: 11;\n}\n.xterm .xterm-scrollable-element > .xterm-invisible {\n\topacity: 0;\n\tpointer-events: none;\n}\n.xterm .xterm-scrollable-element > .xterm-invisible.xterm-fade {\n\ttransition: opacity 800ms linear;\n}\n\n/* Scrollable Content Inset Shadow */\n.xterm .xterm-scrollable-element > .xterm-shadow {\n\tposition: absolute;\n\tdisplay: none;\n}\n.xterm .xterm-scrollable-element > .xterm-shadow.xterm-shadow-top {\n\tdisplay: block;\n\ttop: 0;\n\tleft: 3px;\n\theight: 3px;\n\twidth: 100%;\n\tbox-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset;\n}\n.xterm .xterm-scrollable-element > .xterm-shadow.xterm-shadow-left {\n\tdisplay: block;\n\ttop: 3px;\n\tleft: 0;\n\theight: 100%;\n\twidth: 3px;\n\tbox-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset;\n}\n.xterm .xterm-scrollable-element > .xterm-shadow.xterm-shadow-top-left-corner {\n\tdisplay: block;\n\ttop: 0;\n\tleft: 0;\n\theight: 3px;\n\twidth: 3px;\n}\n.xterm .xterm-scrollable-element > .xterm-shadow.xterm-shadow-top.xterm-shadow-left {\n\tbox-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset;\n}\n"
  },
  {
    "path": "demo/client/client.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This file is the entry point for browserify.\n */\n\n// HACK: Playwright/WebKit on Windows does not support WebAssembly https://stackoverflow.com/q/62311688/1156119\nimport type { ImageAddon as ImageAddonType } from '@xterm/addon-image';\nlet ImageAddon: typeof ImageAddonType | undefined; // eslint-disable-line @typescript-eslint/naming-convention\nif ('WebAssembly' in window) {\n  const imageAddon = require('@xterm/addon-image');\n  ImageAddon = imageAddon.ImageAddon;\n}\n\nimport { Terminal, ITerminalOptions, type ITheme } from '@xterm/xterm';\nimport { AttachAddon } from '@xterm/addon-attach';\nimport { AddonImageWindow } from './components/window/addonImageWindow';\nimport { AddonLigaturesWindow } from './components/window/addonLigaturesWindow';\nimport { AddonProgressWindow } from './components/window/addonProgressWindow';\nimport { AddonSearchWindow } from './components/window/addonSearchWindow';\nimport { AddonSerializeWindow } from './components/window/addonSerializeWindow';\nimport { AddonWebFontsWindow } from './components/window/addonWebFontsWindow';\nimport { AddonWebLinksWindow } from './components/window/addonWebLinksWindow';\nimport { AddonsWindow } from './components/window/addonsWindow';\nimport { CellInspectorWindow } from './components/window/cellInspectorWindow';\nimport { ControlBar } from './components/controlBar';\nimport { WebglWindow } from './components/window/webglWindow';\nimport { OptionsWindow } from './components/window/optionsWindow';\nimport { StyleWindow } from './components/window/styleWindow';\nimport { TestWindow } from './components/window/testWindow';\nimport { VtWindow } from './components/window/vtWindow';\nimport { ClipboardAddon } from '@xterm/addon-clipboard';\nimport { FitAddon } from '@xterm/addon-fit';\nimport { LigaturesAddon } from '@xterm/addon-ligatures';\nimport { ProgressAddon } from '@xterm/addon-progress';\nimport { SearchAddon, ISearchOptions } from '@xterm/addon-search';\nimport { SerializeAddon } from '@xterm/addon-serialize';\nimport { WebFontsAddon } from '@xterm/addon-web-fonts';\nimport { WebLinksAddon } from '@xterm/addon-web-links';\nimport { WebglAddon } from '@xterm/addon-webgl';\nimport { Unicode11Addon } from '@xterm/addon-unicode11';\nimport { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';\nimport { AddonCollection, type AddonType, type IDemoAddon } from './types';\n\nexport interface IWindowWithTerminal extends Window {\n  term: typeof Terminal;\n  Terminal: typeof Terminal;\n  AttachAddon?: typeof AttachAddon;\n  ClipboardAddon?: typeof ClipboardAddon;\n  FitAddon?: typeof FitAddon;\n  ImageAddon?: typeof ImageAddon;\n  ProgressAddon?: typeof ProgressAddon;\n  SearchAddon?: typeof SearchAddon;\n  SerializeAddon?: typeof SerializeAddon;\n  WebLinksAddon?: typeof WebLinksAddon;\n  WebglAddon?: typeof WebglAddon;\n  Unicode11Addon?: typeof Unicode11Addon;\n  UnicodeGraphemesAddon?: typeof UnicodeGraphemesAddon;\n  LigaturesAddon?: typeof LigaturesAddon;\n}\ndeclare let window: IWindowWithTerminal;\n\nlet term: Terminal | null;\nlet protocol: string;\nlet socketURL: string;\nlet socket: WebSocket | null;\nlet pid: string;\nlet controlBar: ControlBar;\nlet addonsWindow: AddonsWindow;\nlet addonSearchWindow: AddonSearchWindow;\nlet addonWebglWindow: WebglWindow;\nlet optionsWindow: OptionsWindow;\n\nconst addons: AddonCollection = {\n  attach: { name: 'attach', ctor: AttachAddon, canChange: false },\n  clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true },\n  fit: { name: 'fit', ctor: FitAddon, canChange: false },\n  image: { name: 'image', ctor: ImageAddon!, canChange: true },\n  progress: { name: 'progress', ctor: ProgressAddon, canChange: true },\n  search: { name: 'search', ctor: SearchAddon, canChange: true },\n  serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },\n  webFonts: { name: 'webFonts', ctor: WebFontsAddon, canChange: true },\n  webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true },\n  webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },\n  unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true },\n  unicodeGraphemes: { name: 'unicodeGraphemes', ctor: UnicodeGraphemesAddon, canChange: true },\n  ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true }\n};\n\nlet terminalContainer = document.getElementById('terminal-container');\nlet actionElements: {\n  findNext: HTMLInputElement;\n  findPrevious: HTMLInputElement;\n  findResults: HTMLElement;\n};\nlet paddingElement: HTMLInputElement;\n\nconst xtermjsTheme = {\n  foreground: '#F8F8F8',\n  background: '#2D2E2C',\n  selectionBackground: '#5DA5D533',\n  selectionInactiveBackground: '#555555AA',\n  black: '#1E1E1D',\n  brightBlack: '#262625',\n  red: '#CE5C5C',\n  brightRed: '#FF7272',\n  green: '#5BCC5B',\n  brightGreen: '#72FF72',\n  yellow: '#CCCC5B',\n  brightYellow: '#FFFF72',\n  blue: '#5D5DD3',\n  brightBlue: '#7279FF',\n  magenta: '#BC5ED1',\n  brightMagenta: '#E572FF',\n  cyan: '#5DA5D5',\n  brightCyan: '#72F0FF',\n  white: '#F8F8F8',\n  brightWhite: '#FFFFFF'\n} satisfies ITheme;\nfunction setPadding(): void {\n  term!.element!.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';\n  addons.fit.instance!.fit();\n}\n\nfunction getSearchOptions(): ISearchOptions {\n  return {\n    regex: (document.getElementById('regex') as HTMLInputElement).checked,\n    wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,\n    caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked,\n    decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? {\n      matchBackground: '#232422',\n      matchBorder: '#555753',\n      matchOverviewRuler: '#555753',\n      activeMatchBackground: '#ef2929',\n      activeMatchBorder: '#ffffff',\n      activeMatchColorOverviewRuler: '#ef2929'\n    } : undefined\n  };\n}\n\nconst disposeRecreateButtonHandler: () => void = () => {\n  // If the terminal exists dispose of it, otherwise recreate it\n  if (term) {\n    term.dispose();\n    term = null;\n    (window as any).term = null;\n    socket = null;\n    addons.attach.instance = undefined;\n    addons.clipboard.instance = undefined;\n    addons.fit.instance = undefined;\n    addons.image.instance = undefined;\n    addons.search.instance = undefined;\n    addons.serialize.instance = undefined;\n    addons.unicode11.instance = undefined;\n    addons.unicodeGraphemes.instance = undefined;\n    addons.ligatures.instance = undefined;\n    addons.webLinks.instance = undefined;\n    addons.webgl.instance = undefined;\n    document.getElementById('dispose')!.innerHTML = 'Recreate Terminal';\n  } else {\n    createTerminal();\n    document.getElementById('dispose')!.innerHTML = 'Dispose terminal';\n  }\n};\n\nconst createNewWindowButtonHandler: () => void = () => {\n  if (term) {\n    disposeRecreateButtonHandler();\n  }\n  const win = window.open()!;\n  terminalContainer = win.document.createElement('div');\n  terminalContainer.id = 'terminal-container';\n  win.document.body.appendChild(terminalContainer);\n\n  // Stylesheets are needed to get the terminal in the popout window to render\n  // correctly. We also need to wait for them to load before creating the\n  // terminal, otherwise we will not compute the correct metrics when rendering.\n  let pendingStylesheets = 0;\n  for (const linkNode of document.querySelectorAll('head link[rel=stylesheet]')) {\n    const newLink = document.createElement('link');\n    newLink.rel = 'stylesheet';\n    newLink.href = (linkNode as HTMLLinkElement).href;\n    win.document.head.appendChild(newLink);\n\n    pendingStylesheets++;\n    newLink.addEventListener('load', () => {\n      pendingStylesheets--;\n      if (pendingStylesheets === 0) {\n        createTerminal();\n      }\n    });\n  }\n};\n\nif (document.location.pathname === '/test') {\n  window.Terminal = Terminal;\n  window.AttachAddon = AttachAddon;\n  window.ClipboardAddon = ClipboardAddon;\n  window.FitAddon = FitAddon;\n  window.ImageAddon = ImageAddon;\n  window.ProgressAddon = ProgressAddon;\n  window.SearchAddon = SearchAddon;\n  window.SerializeAddon = SerializeAddon;\n  window.Unicode11Addon = Unicode11Addon;\n  window.UnicodeGraphemesAddon = UnicodeGraphemesAddon;\n  window.LigaturesAddon = LigaturesAddon;\n  window.WebLinksAddon = WebLinksAddon;\n  window.WebglAddon = WebglAddon;\n} else {\n  const typedTerm = createTerminal();\n\n  controlBar = new ControlBar(document.getElementById('sidebar')!, document.querySelector('.banner-tabs')!, []);\n  optionsWindow = controlBar.registerWindow(new OptionsWindow(typedTerm, addons, { updateTerminalSize, updateTerminalContainerBackground }));\n  const styleWindow = controlBar.registerWindow(new StyleWindow(typedTerm, addons));\n  controlBar.registerWindow(new CellInspectorWindow(typedTerm, addons));\n  controlBar.registerWindow(new VtWindow(typedTerm, addons));\n  addonsWindow = controlBar.registerWindow(new AddonsWindow(typedTerm, addons));\n  controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true });\n  controlBar.registerWindow(new AddonLigaturesWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });\n  controlBar.registerWindow(new AddonProgressWindow(typedTerm, addons), { afterId: 'addon-ligatures', hidden: true, italics: true });\n  addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addon-progress', hidden: true, italics: true });\n  controlBar.registerWindow(new AddonSerializeWindow(typedTerm, addons), { afterId: 'addon-search', hidden: true, italics: true });\n  controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true });\n  controlBar.registerWindow(new AddonWebLinksWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true });\n  addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-links', hidden: true, italics: true });\n  controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler }), { afterId: 'options' });\n  actionElements = {\n    findNext: addonSearchWindow.findNextInput,\n    findPrevious: addonSearchWindow.findPreviousInput,\n    findResults: addonSearchWindow.findResultsSpan\n  };\n  controlBar.activateDefaultTab();\n\n  // TODO: Most of below should be encapsulated within windows\n  paddingElement = styleWindow.paddingElement;\n\n  controlBar.setTabVisible('addon-image', !!addons.image.instance);\n  controlBar.setTabVisible('addon-ligatures', !!addons.ligatures.instance);\n  controlBar.setTabVisible('addon-progress', !!addons.progress.instance);\n  controlBar.setTabVisible('addon-search', true);\n  controlBar.setTabVisible('addon-serialize', true);\n  controlBar.setTabVisible('addon-web-fonts', true);\n  controlBar.setTabVisible('addon-web-links', !!addons.webLinks.instance);\n  controlBar.setTabVisible('addon-webgl', true);\n  addonWebglWindow.setTextureAtlas(addons.webgl.instance!.textureAtlas!);\n  addons.webgl.instance!.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));\n  addons.webgl.instance!.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));\n  addons.webgl.instance!.onRemoveTextureAtlasCanvas(e => addonWebglWindow.removeTextureAtlas(e));\n\n  paddingElement.value = '0';\n  addDomListener(paddingElement, 'change', setPadding);\n  addDomListener(actionElements.findNext, 'keydown', (e) => {\n    if (e.key === 'Enter') {\n      addons.search.instance!.findNext(actionElements.findNext.value, getSearchOptions());\n      e.preventDefault();\n    }\n  });\n  addDomListener(actionElements.findNext, 'input', (e) => {\n    addons.search.instance!.findNext(actionElements.findNext.value, getSearchOptions());\n  });\n  addDomListener(actionElements.findPrevious, 'keydown', (e) => {\n    if (e.key === 'Enter') {\n      addons.search.instance!.findPrevious(actionElements.findPrevious.value, getSearchOptions());\n      e.preventDefault();\n    }\n  });\n  addDomListener(actionElements.findPrevious, 'input', (e) => {\n    addons.search.instance!.findPrevious(actionElements.findPrevious.value, getSearchOptions());\n  });\n  addDomListener(actionElements.findNext, 'blur', (e) => {\n    addons.search.instance!.clearActiveDecoration();\n  });\n  addDomListener(actionElements.findPrevious, 'blur', (e) => {\n    addons.search.instance!.clearActiveDecoration();\n  });\n}\n\nfunction createTerminal(): Terminal {\n  // Clean terminal\n  while (terminalContainer!.children.length) {\n    terminalContainer!.removeChild(terminalContainer!.children[0]);\n  }\n\n  const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;\n  term = new Terminal({\n    scrollbar: { showScrollbar: true },\n    allowProposedApi: true,\n    windowsPty: isWindows ? {\n      // In a real scenario, these values should be verified on the backend\n      backend: 'conpty',\n      buildNumber: 22621\n    } : undefined,\n    fontFamily: '\"Fira Code\", monospace, \"Powerline Extra Symbols\"',\n    theme: { ...xtermjsTheme }\n  } as ITerminalOptions);\n\n  // Load addons\n  const typedTerm = term as Terminal;\n  addons.search.instance = new SearchAddon();\n  addons.serialize.instance = new SerializeAddon();\n  addons.fit.instance = new FitAddon();\n  addons.image.instance = new ImageAddon!();\n  addons.progress.instance = new ProgressAddon();\n  addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon();\n  addons.clipboard.instance = new ClipboardAddon();\n  try {  // try to start with webgl renderer (might throw on older safari/webkit)\n    addons.webgl.instance = new WebglAddon();\n  } catch (e) {\n    console.warn(e);\n  }\n  addons.webLinks.instance = new WebLinksAddon();\n  addons.webFonts.instance = new WebFontsAddon();\n  typedTerm.loadAddon(addons.fit.instance);\n  typedTerm.loadAddon(addons.image.instance);\n  typedTerm.loadAddon(addons.progress.instance);\n  typedTerm.loadAddon(addons.search.instance);\n  typedTerm.loadAddon(addons.serialize.instance);\n  typedTerm.loadAddon(addons.unicodeGraphemes.instance);\n  typedTerm.loadAddon(addons.webLinks.instance);\n  typedTerm.loadAddon(addons.webFonts.instance);\n  typedTerm.loadAddon(addons.clipboard.instance);\n\n  (window as any).term = term;  // Expose `term` to window for debugging purposes\n  term.onResize((size: { cols: number, rows: number }) => {\n    if (!pid) {\n      return;\n    }\n    const cols = size.cols;\n    const rows = size.rows;\n    const pixelWidth = Math.round(term!.dimensions?.css?.canvas?.width ?? 0);\n    const pixelHeight = Math.round(term!.dimensions?.css?.canvas?.height ?? 0);\n    const url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows + '&pixelWidth=' + pixelWidth + '&pixelHeight=' + pixelHeight;\n\n    fetch(url, { method: 'POST' });\n  });\n  protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';\n  socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';\n\n  addons.fit.instance!.fit();\n\n  if (addons.webgl.instance) {\n    try {\n      typedTerm.loadAddon(addons.webgl.instance);\n      term.open(terminalContainer!);\n    } catch (e) {\n      console.warn('error during loading webgl addon:', e);\n      addons.webgl.instance.dispose();\n      addons.webgl.instance = undefined;\n    }\n  }\n  if (!typedTerm.element) {\n    // webgl loading failed for some reason, attach with DOM renderer\n    term.open(terminalContainer!);\n  }\n\n  term.focus();\n  updateTerminalContainerBackground();\n\n  const resizeObserver = new ResizeObserver(entries => {\n    if (optionsWindow.autoResize) {\n      // In general this should be debounced to avoid excessive work on the main\n      // thread by firing the expensive resize action repeatedly\n      addons.fit.instance!.fit();\n    }\n  });\n  resizeObserver.observe(terminalContainer!);\n\n  window.addEventListener('resize', () => {\n    terminalContainer!.style.width = document.body.clientWidth + 'px';\n  });\n\n  // fit is called within a setTimeout, cols and rows need this.\n  setTimeout(async () => {\n    optionsWindow.initOptions(addDomListener);\n\n    // Set terminal size again to set the specific dimensions on the demo\n    updateTerminalSize();\n\n    const useRealTerminal = document.getElementById('use-real-terminal');\n    if (useRealTerminal instanceof HTMLInputElement && !useRealTerminal.checked) {\n      runFakeTerminal();\n    } else {\n      const pixelWidth = Math.round(term!.dimensions?.css?.canvas?.width ?? 0);\n      const pixelHeight = Math.round(term!.dimensions?.css?.canvas?.height ?? 0);\n      const res = await fetch('/terminals?cols=' + term!.cols + '&rows=' + term!.rows + '&pixelWidth=' + pixelWidth + '&pixelHeight=' + pixelHeight, { method: 'POST' });\n      const processId = await res.text();\n      pid = processId;\n      socketURL += processId;\n      socket = new WebSocket(socketURL);\n      socket.onopen = runRealTerminal;\n      socket.onclose = runFakeTerminal;\n      socket.onerror = runFakeTerminal;\n    }\n  }, 0);\n\n  return typedTerm;\n}\n\nfunction runRealTerminal(): void {\n  addons.attach.instance = new AttachAddon(socket!);\n  term!.loadAddon(addons.attach.instance);\n  (term as any)._initialized = true;\n  initAddons(term!);\n}\n\nfunction runFakeTerminal(): void {\n  if ((term as any)._initialized) {\n    return;\n  }\n\n  (term as any)._initialized = true;\n  initAddons(term!);\n\n  (term as any).prompt = () => {\n    term!.write('\\r\\n$ ');\n  };\n\n  term!.writeln('Welcome to xterm.js');\n  term!.writeln('This is a local terminal emulation, without a real terminal in the back-end.');\n  term!.writeln('Type some keys and commands to play around.');\n  term!.writeln('');\n  (term as any).prompt();\n\n  term!.onKey((e: { key: string, domEvent: KeyboardEvent }) => {\n    const ev = e.domEvent;\n    const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;\n\n    if (ev.keyCode === 13) {\n      (term as any).prompt();\n    } else if (ev.keyCode === 8) {\n      // Do not delete the prompt\n      if ((term as any)._core.buffer.x > 2) {\n        term!.write('\\b \\b');\n      }\n    } else if (printable) {\n      term!.write(e.key);\n    }\n  });\n}\n\nfunction updateTerminalContainerBackground(): void {\n  if (term!.options.allowTransparency) {\n    terminalContainer!.style.background = 'repeating-conic-gradient(#000000 0% 25%, #101010 0% 50%) 50% / 20px 20px';\n  } else {\n    terminalContainer!.style.background = term!.options.theme?.background ?? '#000000';\n  }\n}\n\nfunction initAddons(term: Terminal): void {\n  const fragment = document.createDocumentFragment();\n\n  function postInitWebgl(): void {\n    controlBar.setTabVisible('addon-webgl', true);\n    setTimeout(() => {\n      addonWebglWindow.setTextureAtlas(addons.webgl.instance!.textureAtlas!);\n      addons.webgl.instance!.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));\n      addons.webgl.instance!.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));\n    }, 500);\n  }\n  function preDisposeWebgl(): void {\n    controlBar.setTabVisible('addon-webgl', false);\n    if (addons.webgl.instance!.textureAtlas) {\n      addons.webgl.instance!.textureAtlas.remove();\n    }\n  }\n\n  (Object.keys(addons) as AddonType[]).forEach(name => {\n    const addon = addons[name];\n    const checkbox = document.createElement('input') as HTMLInputElement;\n    checkbox.type = 'checkbox';\n    checkbox.checked = !!addon.instance;\n    if (!addon.canChange) {\n      checkbox.disabled = true;\n    }\n    if (name === 'unicode11' && checkbox.checked) {\n      term.unicode.activeVersion = '11';\n    }\n    if (name === 'unicodeGraphemes' && checkbox.checked) {\n      term.unicode.activeVersion = '15-graphemes';\n    }\n    if (name === 'search' && checkbox.checked) {\n      addons[name].instance!.onDidChangeResults(e => updateFindResults(e));\n    }\n    addDomListener(checkbox, 'change', () => {\n      if (name === 'image') {\n        if (checkbox.checked) {\n          const ctorOptionsJson = document.querySelector<HTMLTextAreaElement>('#image-options')!.value;\n          addon.instance = ctorOptionsJson\n            ? new addons[name].ctor(JSON.parse(ctorOptionsJson))\n            : new addons[name].ctor();\n          term.loadAddon(addon.instance);\n          controlBar.setTabVisible('addon-image', true);\n        } else {\n          controlBar.setTabVisible('addon-image', false);\n          addon.instance!.dispose();\n          addon.instance = undefined;\n        }\n        return;\n      }\n      if (checkbox.checked) {\n        // HACK: Manually remove addons that cannot be changes\n        addon.instance = new (addon as IDemoAddon<Exclude<AddonType, 'attach'>>).ctor();\n        try {\n          term.loadAddon(addon.instance);\n          if (name === 'webgl') {\n            postInitWebgl();\n          } else if (name === 'unicode11') {\n            term.unicode.activeVersion = '11';\n          } else if (name === 'unicodeGraphemes') {\n            term.unicode.activeVersion = '15-graphemes';\n          } else if (name === 'search') {\n            controlBar.setTabVisible('addon-search', true);\n            addons[name].instance!.onDidChangeResults(e => updateFindResults(e));\n          } else if (name === 'serialize') {\n            controlBar.setTabVisible('addon-serialize', true);\n          } else if (name === 'ligatures') {\n            controlBar.setTabVisible('addon-ligatures', true);\n          } else if (name === 'progress') {\n            controlBar.setTabVisible('addon-progress', true);\n          } else if (name === 'webLinks') {\n            controlBar.setTabVisible('addon-web-links', true);\n          }\n        }\n        catch {\n          addon.instance = undefined;\n          checkbox.checked = false;\n          checkbox.disabled = true;\n        }\n      } else {\n        if (name === 'webgl') {\n          preDisposeWebgl();\n        } else if (name === 'unicode11' || name === 'unicodeGraphemes') {\n          term.unicode.activeVersion = '6';\n        } else if (name === 'search') {\n          controlBar.setTabVisible('addon-search', false);\n        } else if (name === 'serialize') {\n          controlBar.setTabVisible('addon-serialize', false);\n        } else if (name === 'ligatures') {\n          controlBar.setTabVisible('addon-ligatures', false);\n        } else if (name === 'progress') {\n          controlBar.setTabVisible('addon-progress', false);\n        } else if (name === 'webLinks') {\n          controlBar.setTabVisible('addon-web-links', false);\n        }\n        addon.instance!.dispose();\n        addon.instance = undefined;\n      }\n      if (name === 'ligatures') {\n        // Recreate webgl when ligatures are toggled so texture atlas picks up any font feature\n        // settings changes\n        if (addons.webgl.instance) {\n          preDisposeWebgl();\n          addons.webgl.instance.dispose();\n          const customGlyphsCheckbox = document.getElementById('webgl-custom-glyphs') as HTMLInputElement;\n          addons.webgl.instance = new addons.webgl.ctor({ customGlyphs: customGlyphsCheckbox?.checked ?? true });\n          term.loadAddon(addons.webgl.instance);\n          postInitWebgl();\n        }\n      }\n    });\n    const label = document.createElement('label');\n    label.classList.add('addon');\n    if (!addon.canChange) {\n      label.title = 'This addon is needed for the demo to operate';\n    }\n    label.appendChild(checkbox);\n    label.appendChild(document.createTextNode(name));\n    const wrapper = document.createElement('div');\n    wrapper.classList.add('addon');\n    wrapper.appendChild(label);\n\n    // Add customGlyphs sub-checkbox for webgl addon\n    if (name === 'webgl') {\n      const customGlyphsCheckbox = document.createElement('input') as HTMLInputElement;\n      customGlyphsCheckbox.type = 'checkbox';\n      customGlyphsCheckbox.checked = true; // Default to enabled\n      customGlyphsCheckbox.id = 'webgl-custom-glyphs';\n      addDomListener(customGlyphsCheckbox, 'change', () => {\n        if (addons.webgl.instance) {\n          preDisposeWebgl();\n          addons.webgl.instance.dispose();\n          addons.webgl.instance = new addons.webgl.ctor({ customGlyphs: customGlyphsCheckbox.checked });\n          term.loadAddon(addons.webgl.instance);\n          postInitWebgl();\n        }\n      });\n      const customGlyphsLabel = document.createElement('label');\n      customGlyphsLabel.classList.add('addon');\n      customGlyphsLabel.style.display = 'block';\n      customGlyphsLabel.style.marginLeft = '20px';\n      customGlyphsLabel.appendChild(customGlyphsCheckbox);\n      customGlyphsLabel.appendChild(document.createTextNode('customGlyphs'));\n      wrapper.appendChild(customGlyphsLabel);\n    }\n\n    fragment.appendChild(wrapper);\n  });\n  const container = addonsWindow.addonsContainer;\n  container.innerHTML = '';\n  container.appendChild(fragment);\n}\n\nfunction updateFindResults(e: { resultIndex: number, resultCount: number } | undefined): void {\n  let content: string;\n  if (e === undefined) {\n    content = 'undefined';\n  } else {\n    content = `index: ${e.resultIndex}, count: ${e.resultCount}`;\n  }\n  actionElements.findResults.textContent = content;\n}\n\nfunction addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {\n  element.addEventListener(type, handler);\n  (term as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });\n}\n\nfunction updateTerminalSize(): void {\n  const showScrollbar = term!.options.scrollbar?.showScrollbar ?? true;\n  const scrollBarWidth = (term!.options.scrollback === 0 || !showScrollbar)\n    ? 0\n    : (term!.options.scrollbar?.width ?? 14);\n  const width = optionsWindow.autoResize ? '100%'\n    : ((term as any).dimensions.css.canvas.width + scrollBarWidth).toString() + 'px';\n  const height = optionsWindow.autoResize ? '100%'\n    : ((term as any).dimensions.css.canvas.height).toString() + 'px';\n  terminalContainer!.style.width = width;\n  terminalContainer!.style.height = height;\n  addons.fit.instance!.fit();\n}\n\n(console as any).image = (source: ImageData | HTMLCanvasElement, scale: number = 1) => {\n  function getBox(width: number, height: number): any {\n    return {\n      string: '+',\n      style: 'font-size: 1px; padding: ' + Math.floor(height / 2) + 'px ' + Math.floor(width / 2) + 'px; line-height: ' + height + 'px;'\n    };\n  }\n  if (source instanceof HTMLCanvasElement) {\n    source = source.getContext('2d')!.getImageData(0, 0, source.width, source.height)!;\n  }\n  const canvas = document.createElement('canvas');\n  canvas.width = source.width;\n  canvas.height = source.height;\n  const ctx = canvas.getContext('2d')!;\n  ctx.putImageData(source, 0, 0);\n\n  const sw = source.width * scale;\n  const sh = source.height * scale;\n  const dim = getBox(sw, sh);\n  console.log(\n    `Image: ${source.width} x ${source.height}\\n%c${dim.string}`,\n    `${dim.style}background: url(${canvas.toDataURL()}); background-size: ${sw}px ${sh}px; background-repeat: no-repeat; color: transparent;`\n  );\n  console.groupCollapsed('Zoomed');\n  console.log(\n    `%c${dim.string}`,\n    `${getBox(sw * 10, sh * 10).style}background: url(${canvas.toDataURL()}); background-size: ${sw * 10}px ${sh * 10}px; background-repeat: no-repeat; color: transparent; image-rendering: pixelated;-ms-interpolation-mode: nearest-neighbor;`\n  );\n  console.groupEnd();\n};\n"
  },
  {
    "path": "demo/client/components/controlBar.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal } from '@xterm/xterm';\n\nexport interface ITabConfig {\n  id: string;\n  label: string;\n}\n\nexport interface IControlWindow {\n  readonly id: string;\n  readonly label: string;\n  build(container: HTMLElement): void;\n  setTerminal(terminal: Terminal): void;\n}\n\nexport class ControlBar {\n  private readonly _sidebar: HTMLElement;\n  private readonly _resizeHandleH: HTMLElement;\n  private readonly _resizeHandleV: HTMLElement;\n  private readonly _resizeHandleCorner: HTMLElement;\n  private _resizeMode: 'none' | 'horizontal' | 'vertical' | 'corner' = 'none';\n\n  private readonly _tabContainer: HTMLElement;\n  private readonly _tabs: Map<string, { button: HTMLButtonElement, content: HTMLElement }> = new Map();\n  private _activeTabId: string | null = null;\n\n  constructor(sidebar: HTMLElement, tabContainer: HTMLElement, tabs: ITabConfig[]) {\n    this._sidebar = sidebar;\n    this._tabContainer = tabContainer;\n\n    // Create resize handles\n    this._resizeHandleH = document.createElement('div');\n    this._resizeHandleH.id = 'sidebar-resize-handle-horizontal';\n\n    this._resizeHandleV = document.createElement('div');\n    this._resizeHandleV.id = 'sidebar-resize-handle-vertical';\n\n    this._resizeHandleCorner = document.createElement('div');\n    this._resizeHandleCorner.id = 'sidebar-resize-handle-corner';\n\n    // Insert handles at the beginning of the sidebar\n    this._sidebar.prepend(this._resizeHandleCorner);\n    this._sidebar.prepend(this._resizeHandleV);\n    this._sidebar.prepend(this._resizeHandleH);\n\n    this._initResizeListeners();\n    this._initTabs(tabs);\n  }\n\n  private _initResizeListeners(): void {\n    this._resizeHandleH.addEventListener('mousedown', (e: MouseEvent) => this._startResize('horizontal', e));\n    this._resizeHandleV.addEventListener('mousedown', (e: MouseEvent) => this._startResize('vertical', e));\n    this._resizeHandleCorner.addEventListener('mousedown', (e: MouseEvent) => this._startResize('corner', e));\n\n    document.addEventListener('mousemove', (e: MouseEvent) => {\n      if (this._resizeMode === 'none') return;\n      if (this._resizeMode === 'horizontal' || this._resizeMode === 'corner') {\n        const newWidth = window.innerWidth - e.clientX - 10;\n        this._sidebar.style.width = `${Math.max(200, newWidth)}px`;\n      }\n      if (this._resizeMode === 'vertical' || this._resizeMode === 'corner') {\n        const rect = this._sidebar.getBoundingClientRect();\n        const newHeight = e.clientY - rect.top;\n        this._sidebar.style.height = `${Math.max(100, newHeight)}px`;\n      }\n    });\n\n    document.addEventListener('mouseup', () => {\n      this._resizeMode = 'none';\n      document.body.style.cursor = '';\n      document.body.style.userSelect = '';\n    });\n  }\n\n  private _startResize(mode: 'horizontal' | 'vertical' | 'corner', e: MouseEvent): void {\n    this._resizeMode = mode;\n    document.body.style.cursor = mode === 'horizontal' ? 'ew-resize' : mode === 'vertical' ? 'ns-resize' : 'nesw-resize';\n    document.body.style.userSelect = 'none';\n    e.preventDefault();\n  }\n\n  private _initTabs(tabs: ITabConfig[]): void {\n    // Clear existing buttons\n    this._tabContainer.innerHTML = '';\n\n    // Create tab buttons\n    for (const tab of tabs) {\n      const content = document.getElementById(tab.id);\n      if (!content) {\n        console.warn(`Tab content element not found: ${tab.id}`);\n        continue;\n      }\n\n      const button = document.createElement('button');\n      button.id = `${tab.id}button`;\n      button.className = 'tabLinks';\n      button.textContent = tab.label;\n      button.addEventListener('click', (e) => this._openSection(e, tab.id));\n\n      this._tabContainer.appendChild(button);\n      this._tabs.set(tab.id, { button, content });\n\n      // Hide content initially\n      content.style.display = 'none';\n    }\n\n    // Restore saved tab or default to first\n    const savedTab = localStorage.getItem('tab');\n    const tabId = savedTab && this._tabs.has(savedTab) ? savedTab : tabs[0]?.id;\n    if (tabId) {\n      this._activateTab(tabId);\n    }\n  }\n\n  private _openSection(event: MouseEvent, tabId: string): void {\n    const tab = this._tabs.get(tabId);\n    if (!tab) return;\n\n    // If clicking active tab, toggle sidebar visibility\n    if (tab.button.classList.contains('active')) {\n      this._sidebar.classList.toggle('sidebar-hidden');\n      return;\n    }\n\n    // Show sidebar if hidden\n    this._sidebar.classList.remove('sidebar-hidden');\n\n    this._activateTab(tabId);\n  }\n\n  private _activateTab(tabId: string): void {\n    // Deactivate all tabs\n    for (const [, { button, content }] of this._tabs) {\n      button.classList.remove('active');\n      content.style.display = 'none';\n    }\n\n    // Activate the selected tab\n    const tab = this._tabs.get(tabId);\n    if (tab) {\n      tab.button.classList.add('active');\n      tab.content.style.display = 'block';\n      this._activeTabId = tabId;\n      localStorage.setItem('tab', tabId);\n    }\n  }\n\n  public registerWindow<T extends IControlWindow>(window: T, options?: { afterId?: string, hidden?: boolean, italics?: boolean }): T {\n    // Create button\n    const button = document.createElement('button');\n    button.id = `${window.id}button`;\n    button.className = 'tabLinks';\n    button.textContent = window.label;\n    button.addEventListener('click', (e) => this._openSection(e, window.id));\n\n    // Apply small tab styling\n    if (options?.italics) {\n      button.style.fontStyle = 'italic';\n    }\n\n    // Insert after specified tab or append at end\n    if (options?.afterId) {\n      const afterTab = this._tabs.get(options.afterId);\n      if (afterTab?.button.nextSibling) {\n        this._tabContainer.insertBefore(button, afterTab.button.nextSibling);\n      } else {\n        this._tabContainer.appendChild(button);\n      }\n    } else {\n      this._tabContainer.appendChild(button);\n    }\n\n    // Hide if specified\n    if (options?.hidden) {\n      button.style.display = 'none';\n    }\n\n    // Create content container\n    const content = document.createElement('div');\n    content.id = window.id;\n    content.className = 'tabContent';\n    content.style.display = 'none';\n    this._sidebar.appendChild(content);\n\n    // Let the window build its content\n    window.build(content);\n\n    this._tabs.set(window.id, { button, content });\n\n    return window;\n  }\n\n  public setTabVisible(tabId: string, visible: boolean): void {\n    const tab = this._tabs.get(tabId);\n    if (tab) {\n      tab.button.style.display = visible ? '' : 'none';\n      // If hiding the active tab, switch to first visible tab\n      if (!visible && this._activeTabId === tabId) {\n        for (const [id, t] of this._tabs) {\n          if (t.button.style.display !== 'none') {\n            this._activateTab(id);\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  public get activeTabId(): string | null {\n    return this._activeTabId;\n  }\n\n  public activateDefaultTab(): void {\n    // Restore saved tab or default to first visible tab\n    const savedTab = localStorage.getItem('tab');\n    if (savedTab && this._tabs.has(savedTab)) {\n      const tab = this._tabs.get(savedTab);\n      if (tab && tab.button.style.display !== 'none') {\n        this._activateTab(savedTab);\n        return;\n      }\n    }\n    // Fall back to first visible tab\n    for (const [id, tab] of this._tabs) {\n      if (tab.button.style.display !== 'none') {\n        this._activateTab(id);\n        return;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonImageWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\nimport type { IImageAddonOptions } from '@xterm/addon-image';\n\nexport class AddonImageWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-image';\n  public readonly label = 'image';\n\n  private _imageStorageLimitInput!: HTMLInputElement;\n  private _imageShowPlaceholderCheckbox!: HTMLInputElement;\n  private _imageOptionsTextarea!: HTMLTextAreaElement;\n\n  public build(container: HTMLElement): void {\n    // Storage limit\n    const storageLimitLabel = document.createElement('label');\n    storageLimitLabel.textContent = 'Storage Limit (in MB) ';\n    this._imageStorageLimitInput = document.createElement('input');\n    this._imageStorageLimitInput.type = 'number';\n    this._imageStorageLimitInput.id = 'image-storagelimit';\n    storageLimitLabel.appendChild(this._imageStorageLimitInput);\n    container.appendChild(storageLimitLabel);\n    container.appendChild(document.createElement('br'));\n\n    // Show placeholder\n    const placeholderLabel = document.createElement('label');\n    placeholderLabel.textContent = 'Show Placeholder ';\n    this._imageShowPlaceholderCheckbox = document.createElement('input');\n    this._imageShowPlaceholderCheckbox.type = 'checkbox';\n    this._imageShowPlaceholderCheckbox.id = 'image-showplaceholder';\n    placeholderLabel.appendChild(this._imageShowPlaceholderCheckbox);\n    container.appendChild(placeholderLabel);\n    container.appendChild(document.createElement('br'));\n    container.appendChild(document.createElement('br'));\n\n    // Ctor options\n    const optionsLabel = document.createElement('label');\n    optionsLabel.appendChild(document.createTextNode('Ctor options (applied on addon relaunch)'));\n    optionsLabel.appendChild(document.createElement('br'));\n    this._imageOptionsTextarea = document.createElement('textarea');\n    this._imageOptionsTextarea.id = 'image-options';\n    this._imageOptionsTextarea.cols = 40;\n    this._imageOptionsTextarea.rows = 12;\n    optionsLabel.appendChild(this._imageOptionsTextarea);\n    container.appendChild(optionsLabel);\n\n    container.appendChild(document.createElement('br'));\n    container.appendChild(document.createElement('br'));\n\n    // Sixel demos\n    const dlSixel = document.createElement('dl');\n    const dtSixel = document.createElement('dt');\n    dtSixel.textContent = 'Sixel';\n    dlSixel.appendChild(dtSixel);\n    this._addDdWithButton(dlSixel, 'image-demo1', 'snake');\n    this._addDdWithButton(dlSixel, 'image-demo2', 'oranges');\n    container.appendChild(dlSixel);\n\n    // IIP demos\n    const dlIip = document.createElement('dl');\n    const dtIip = document.createElement('dt');\n    dtIip.textContent = 'IIP (iTerm)';\n    dlIip.appendChild(dtIip);\n    this._addDdWithButton(dlIip, 'image-demo3', 'palette');\n    container.appendChild(dlIip);\n\n    // Kitty demos\n    const dlKitty = document.createElement('dl');\n    const dtKitty = document.createElement('dt');\n    dtKitty.textContent = 'Kitty';\n    dlKitty.appendChild(dtKitty);\n    this._addDdWithButton(dlKitty, 'image-demo-kitty1', 'palette');\n    container.appendChild(dlKitty);\n\n    this._initImageAddonExposed();\n  }\n\n  public get imageStorageLimitInput(): HTMLInputElement {\n    return this._imageStorageLimitInput;\n  }\n\n  public get imageShowPlaceholderCheckbox(): HTMLInputElement {\n    return this._imageShowPlaceholderCheckbox;\n  }\n\n  public get imageOptionsTextarea(): HTMLTextAreaElement {\n    return this._imageOptionsTextarea;\n  }\n\n  private _addDdWithButton(dl: HTMLElement, id: string, label: string): void {\n    const dd = document.createElement('dd');\n    const button = document.createElement('button');\n    button.id = id;\n    button.textContent = label;\n    dd.appendChild(button);\n    dl.appendChild(dd);\n  }\n\n  private _initImageAddonExposed(): void {\n    const imageAddon = this._addons.image.instance!;\n    const defaultOptions: IImageAddonOptions = (imageAddon as any)._defaultOpts;\n    const limitStorageElement = document.querySelector<HTMLInputElement>('#image-storagelimit')!;\n    limitStorageElement.valueAsNumber = imageAddon.storageLimit;\n    this._addDomListener(limitStorageElement, 'change', () => {\n      try {\n        imageAddon.storageLimit = limitStorageElement.valueAsNumber;\n        limitStorageElement.valueAsNumber = imageAddon.storageLimit;\n        console.log('changed storageLimit to', imageAddon.storageLimit);\n      } catch (e) {\n        limitStorageElement.valueAsNumber = imageAddon.storageLimit;\n        console.log('storageLimit at', imageAddon.storageLimit);\n        throw e;\n      }\n    });\n    const showPlaceholderElement = document.querySelector<HTMLInputElement>('#image-showplaceholder')!;\n    showPlaceholderElement.checked = imageAddon.showPlaceholder;\n    this._addDomListener(showPlaceholderElement, 'change', () => {\n      imageAddon.showPlaceholder = showPlaceholderElement.checked;\n    });\n    const ctorOptionsElement = document.querySelector<HTMLTextAreaElement>('#image-options')!;\n    ctorOptionsElement.value = JSON.stringify(defaultOptions, null, 2);\n\n    const sixelDemo = (url: string) => () => fetch(url)\n      .then(resp => resp.arrayBuffer())\n      .then(buffer => {\n        this._terminal.write('\\r\\n');\n        this._terminal.write(new Uint8Array(buffer));\n      });\n\n    const iipDemo = (url: string) => () => fetch(url)\n      .then(resp => resp.arrayBuffer())\n      .then(buffer => {\n        const data = new Uint8Array(buffer);\n        let sdata = '';\n        for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);\n        this._terminal.write('\\r\\n');\n        this._terminal.write(`\\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\\x1b\\\\`);\n      });\n\n    const kittyDemo = (url: string) => () => fetch(url)\n      .then(resp => resp.arrayBuffer())\n      .then(buffer => {\n        const data = new Uint8Array(buffer);\n        let sdata = '';\n        for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);\n        const payload = btoa(sdata);\n        this._terminal.write('\\r\\n');\n        this._terminal.write(`\\x1b_Ga=T,f=100;${payload}\\x1b\\\\`);\n      });\n\n    document.getElementById('image-demo1')!.addEventListener('click',\n      sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six'));\n    document.getElementById('image-demo2')!.addEventListener('click',\n      sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));\n    document.getElementById('image-demo3')!.addEventListener('click',\n      iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));\n    document.getElementById('image-demo-kitty1')!.addEventListener('click',\n      kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));\n\n    // demo for image retrieval API\n    this._terminal.element!.addEventListener('click', (ev: MouseEvent) => {\n      if (!ev.ctrlKey || !imageAddon) return;\n\n      // TODO...\n      // if (ev.altKey) {\n      //   const sel = term.getSelectionPosition();\n      //   if (sel) {\n      //     addons.image.instance\n      //       .extractCanvasAtBufferRange(term.getSelectionPosition())\n      //       ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank'));\n      //     return;\n      //   }\n      // }\n\n      const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);\n      const x = pos[0] - 1;\n      const y = pos[1] - 1;\n      const canvas = ev.shiftKey\n        // ctrl+shift+click: get single tile\n        ? imageAddon.extractTileAtBufferCell(x, this._terminal.buffer.active.viewportY + y)\n        // ctrl+click: get original image\n        : imageAddon.getImageAtBufferCell(x, this._terminal.buffer.active.viewportY + y);\n      canvas?.toBlob(data => data && window.open(URL.createObjectURL(data), '_blank'));\n    });\n  }\n\n  private _addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {\n    element.addEventListener(type, handler);\n    (this._terminal as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonLigaturesWindow.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class AddonLigaturesWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-ligatures';\n  public readonly label = 'ligatures';\n\n  public build(container: HTMLElement): void {\n    const dl = document.createElement('dl');\n    const dt = document.createElement('dt');\n    dt.textContent = 'Ligatures Addon';\n    dl.appendChild(dt);\n\n    const dd = document.createElement('dd');\n    const button = document.createElement('button');\n    button.id = 'ligatures-test';\n    button.textContent = 'Common ligatures';\n    button.title = 'Write common ligatures sequences';\n    button.addEventListener('click', () => this._ligaturesTest());\n    dd.appendChild(button);\n    dl.appendChild(dd);\n\n    container.appendChild(dl);\n  }\n\n  private _ligaturesTest(): void {\n    this._terminal.write([\n      '',\n      '-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',\n      '=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',\n      '<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',\n      '<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',\n      '<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',\n      '(* *) /* */ [| |] {| |} ++ +++ \\/ /\\ |- -| <!-- <!---',\n      '==== ===== ====== ======= ======== =========',\n      '---- ----- ------ ------- -------- ---------'\n    ].join('\\r\\n'));\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonProgressWindow.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\nimport type { IProgressState } from '@xterm/addon-progress';\n\nexport class AddonProgressWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-progress';\n  public readonly label = 'progress';\n\n  public build(container: HTMLElement): void {\n    const dl = document.createElement('dl');\n    const dt = document.createElement('dt');\n    dt.textContent = 'Progress Addon';\n    dl.appendChild(dt);\n\n    this._addDdWithButton(dl, 'progress-run', 'full set run', '');\n    this._addDdWithButton(dl, 'progress-0', 'state 0: remove', '');\n    this._addDdWithButton(dl, 'progress-1', 'state 1: set 20%', '');\n    this._addDdWithButton(dl, 'progress-2', 'state 2: error', '');\n    this._addDdWithButton(dl, 'progress-3', 'state 3: indeterminate', '');\n    this._addDdWithButton(dl, 'progress-4', 'state 4: pause', '');\n\n    const progressDd = document.createElement('dd');\n    const progressDiv = document.createElement('div');\n    progressDiv.id = 'progress-progress';\n    const progressPercent = document.createElement('div');\n    progressPercent.id = 'progress-percent';\n    const progressIndeterminate = document.createElement('div');\n    progressIndeterminate.id = 'progress-indeterminate';\n    progressDiv.appendChild(progressPercent);\n    progressDiv.appendChild(progressIndeterminate);\n    progressDd.appendChild(progressDiv);\n    dl.appendChild(progressDd);\n\n    const stateDd = document.createElement('dd');\n    const stateDiv = document.createElement('div');\n    stateDiv.id = 'progress-state';\n    stateDiv.textContent = 'State:';\n    stateDd.appendChild(stateDiv);\n    dl.appendChild(stateDd);\n\n    container.appendChild(dl);\n\n    this._initProgress();\n    this._addProgressStyles(container);\n  }\n\n  private _addDdWithButton(dl: HTMLElement, id: string, label: string, title: string): void {\n    const dd = document.createElement('dd');\n    const button = document.createElement('button');\n    button.id = id;\n    button.textContent = label;\n    if (title) {\n      button.title = title;\n    }\n    dd.appendChild(button);\n    dl.appendChild(dd);\n  }\n\n  private _initProgress(): void {\n    const states = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' };\n    const colors = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' };\n\n    function progressHandler({ state, value }: IProgressState): void {\n      // Simulate windows taskbar hack by windows terminal:\n      // Since the taskbar has no means to indicate error/pause state other than by coloring\n      // the current progress, we move 0 to 10% and distribute higher values in the remaining 90 %\n      // NOTE: This is most likely not what you want to do for other progress indicators,\n      //       that have a proper visual state for error/paused.\n      value = Math.min(10 + value * 0.9, 100);\n      document.getElementById('progress-percent')!.style.width = `${value}%`;\n      document.getElementById('progress-percent')!.style.backgroundColor = colors[state];\n      document.getElementById('progress-state')!.innerText = `State: ${states[state]}`;\n\n      document.getElementById('progress-percent')!.style.display = state === 3 ? 'none' : 'block';\n      document.getElementById('progress-indeterminate')!.style.display = state === 3 ? 'block' : 'none';\n    }\n\n    const progressAddon = this._addons.progress.instance!;\n    progressAddon.onChange(progressHandler);\n\n    const initialProgress = progressAddon.progress;\n    progressHandler(initialProgress);\n\n    document.getElementById('progress-run')!.addEventListener('click', async () => {\n      this._terminal.write('\\x1b]9;4;0\\x1b\\\\');\n      for (let i = 0; i <= 100; i += 5) {\n        this._terminal.write(`\\x1b]9;4;1;${i}\\x1b\\\\`);\n        await new Promise(res => setTimeout(res, 200));\n      }\n    });\n    document.getElementById('progress-0')!.addEventListener('click', () => this._terminal.write('\\x1b]9;4;0\\x1b\\\\'));\n    document.getElementById('progress-1')!.addEventListener('click', () => this._terminal.write('\\x1b]9;4;1;20\\x1b\\\\'));\n    document.getElementById('progress-2')!.addEventListener('click', () => this._terminal.write('\\x1b]9;4;2\\x1b\\\\'));\n    document.getElementById('progress-3')!.addEventListener('click', () => this._terminal.write('\\x1b]9;4;3\\x1b\\\\'));\n    document.getElementById('progress-4')!.addEventListener('click', () => this._terminal.write('\\x1b]9;4;4\\x1b\\\\'));\n  }\n\n  private _addProgressStyles(container: HTMLElement): void {\n    const style = document.createElement('style');\n    style.textContent = `\n      #progress-progress {\n        border: 1px solid black;\n        height: 10px;\n      }\n      #progress-percent {\n        height: 100%;\n      }\n      #progress-indeterminate {\n        display: none;\n        position: relative;\n        height: 100%;\n      }\n      #progress-indeterminate:before {\n        content: '';\n        position: absolute;\n        left: 0;\n        bottom: 0px;\n        width: 50px;\n        height: 10px;\n        background: blue;\n        animation: ballbns 1s ease-in-out infinite alternate;\n      }\n      @keyframes ballbns {\n        0% {  left: 0; transform: translateX(0%); }\n        100% {  left: 100%; transform: translateX(-100%); }\n      }\n    `;\n    container.appendChild(style);\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonSearchWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class AddonSearchWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-search';\n  public readonly label = 'search';\n\n  private _findNextInput!: HTMLInputElement;\n  private _findPreviousInput!: HTMLInputElement;\n  private _findResultsSpan!: HTMLElement;\n  private _regexCheckbox!: HTMLInputElement;\n  private _caseSensitiveCheckbox!: HTMLInputElement;\n  private _wholeWordCheckbox!: HTMLInputElement;\n  private _highlightAllMatchesCheckbox!: HTMLInputElement;\n\n  public build(container: HTMLElement): void {\n    const wrapper = document.createElement('div');\n    wrapper.style.display = 'flex';\n    wrapper.style.flexDirection = 'column';\n\n    // Find next\n    const findNextLabel = document.createElement('label');\n    findNextLabel.textContent = 'Find next ';\n    this._findNextInput = document.createElement('input');\n    this._findNextInput.id = 'find-next';\n    findNextLabel.appendChild(this._findNextInput);\n    wrapper.appendChild(findNextLabel);\n\n    // Find previous\n    const findPrevLabel = document.createElement('label');\n    findPrevLabel.textContent = 'Find previous ';\n    this._findPreviousInput = document.createElement('input');\n    this._findPreviousInput.id = 'find-previous';\n    findPrevLabel.appendChild(this._findPreviousInput);\n    wrapper.appendChild(findPrevLabel);\n\n    // Results\n    const resultsDiv = document.createElement('div');\n    resultsDiv.textContent = 'Results: ';\n    this._findResultsSpan = document.createElement('span');\n    this._findResultsSpan.id = 'find-results';\n    resultsDiv.appendChild(this._findResultsSpan);\n    wrapper.appendChild(resultsDiv);\n\n    // Regex checkbox\n    const regexLabel = document.createElement('label');\n    this._regexCheckbox = document.createElement('input');\n    this._regexCheckbox.type = 'checkbox';\n    this._regexCheckbox.id = 'regex';\n    regexLabel.appendChild(this._regexCheckbox);\n    regexLabel.appendChild(document.createTextNode('Use regex'));\n    wrapper.appendChild(regexLabel);\n\n    // Case sensitive checkbox\n    const caseLabel = document.createElement('label');\n    this._caseSensitiveCheckbox = document.createElement('input');\n    this._caseSensitiveCheckbox.type = 'checkbox';\n    this._caseSensitiveCheckbox.id = 'case-sensitive';\n    caseLabel.appendChild(this._caseSensitiveCheckbox);\n    caseLabel.appendChild(document.createTextNode('Case sensitive'));\n    wrapper.appendChild(caseLabel);\n\n    // Whole word checkbox\n    const wholeWordLabel = document.createElement('label');\n    this._wholeWordCheckbox = document.createElement('input');\n    this._wholeWordCheckbox.type = 'checkbox';\n    this._wholeWordCheckbox.id = 'whole-word';\n    wholeWordLabel.appendChild(this._wholeWordCheckbox);\n    wholeWordLabel.appendChild(document.createTextNode('Whole word'));\n    wrapper.appendChild(wholeWordLabel);\n\n    // Highlight all matches checkbox\n    const highlightLabel = document.createElement('label');\n    this._highlightAllMatchesCheckbox = document.createElement('input');\n    this._highlightAllMatchesCheckbox.type = 'checkbox';\n    this._highlightAllMatchesCheckbox.id = 'highlight-all-matches';\n    this._highlightAllMatchesCheckbox.checked = true;\n    highlightLabel.appendChild(this._highlightAllMatchesCheckbox);\n    highlightLabel.appendChild(document.createTextNode('Highlight All Matches'));\n    wrapper.appendChild(highlightLabel);\n\n    container.appendChild(wrapper);\n  }\n\n  public get findNextInput(): HTMLInputElement {\n    return this._findNextInput;\n  }\n\n  public get findPreviousInput(): HTMLInputElement {\n    return this._findPreviousInput;\n  }\n\n  public get findResultsSpan(): HTMLElement {\n    return this._findResultsSpan;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonSerializeWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class AddonSerializeWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-serialize';\n  public readonly label = 'serialize';\n\n  private _serializeOutputPre!: HTMLPreElement;\n  private _htmlSerializeOutputPre!: HTMLPreElement;\n  private _htmlSerializeOutputResult!: HTMLElement;\n  private _writeToTerminalCheckbox!: HTMLInputElement;\n\n  public build(container: HTMLElement): void {\n    const wrapper = document.createElement('div');\n\n    // Serialize button\n    const serializeBtn = document.createElement('button');\n    serializeBtn.id = 'serialize';\n    serializeBtn.textContent = 'Serialize the content of terminal';\n    serializeBtn.addEventListener('click', () => this._serializeButtonHandler());\n    wrapper.appendChild(serializeBtn);\n\n    // Write to terminal checkbox\n    const writeLabel = document.createElement('label');\n    this._writeToTerminalCheckbox = document.createElement('input');\n    this._writeToTerminalCheckbox.type = 'checkbox';\n    this._writeToTerminalCheckbox.id = 'write-to-terminal';\n    writeLabel.appendChild(this._writeToTerminalCheckbox);\n    writeLabel.appendChild(document.createTextNode('Write back to terminal'));\n    wrapper.appendChild(writeLabel);\n\n    // Serialize output\n    const outputDiv = document.createElement('div');\n    this._serializeOutputPre = document.createElement('pre');\n    this._serializeOutputPre.id = 'serialize-output';\n    outputDiv.appendChild(this._serializeOutputPre);\n    wrapper.appendChild(outputDiv);\n\n    // HTML serialize button\n    const htmlSerializeBtn = document.createElement('button');\n    htmlSerializeBtn.id = 'htmlserialize';\n    htmlSerializeBtn.textContent = 'Serialize the content of terminal in HTML';\n    htmlSerializeBtn.addEventListener('click', () => this._htmlSerializeButtonHandler());\n    wrapper.appendChild(htmlSerializeBtn);\n\n    // HTML serialize result\n    this._htmlSerializeOutputResult = document.createElement('span');\n    this._htmlSerializeOutputResult.id = 'htmlserialize-output-result';\n    wrapper.appendChild(this._htmlSerializeOutputResult);\n\n    // HTML serialize output\n    const htmlOutputDiv = document.createElement('div');\n    this._htmlSerializeOutputPre = document.createElement('pre');\n    this._htmlSerializeOutputPre.id = 'htmlserialize-output';\n    htmlOutputDiv.appendChild(this._htmlSerializeOutputPre);\n    wrapper.appendChild(htmlOutputDiv);\n\n    container.appendChild(wrapper);\n  }\n\n  private _serializeButtonHandler(): void {\n    const output = this._addons.serialize.instance!.serialize();\n    const outputString = JSON.stringify(output);\n\n    this._serializeOutputPre.innerText = outputString;\n    if (this._writeToTerminalCheckbox.checked) {\n      this._terminal.reset();\n      this._terminal.write(output);\n    }\n  }\n\n  private _htmlSerializeButtonHandler(): void {\n    const output = this._addons.serialize.instance!.serializeAsHTML();\n    this._htmlSerializeOutputPre.innerText = output;\n\n    // Deprecated, but the most supported for now.\n    function listener(e: any): void {\n      e.clipboardData.setData('text/html', output);\n      e.preventDefault();\n    }\n    document.addEventListener('copy', listener);\n    document.execCommand('copy');\n    document.removeEventListener('copy', listener);\n    this._htmlSerializeOutputResult.innerText = 'Copied to clipboard';\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonWebFontsWindow.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { loadFonts } from '@xterm/addon-web-fonts';\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class AddonWebFontsWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-web-fonts';\n  public readonly label = 'web-fonts';\n\n  public build(container: HTMLElement): void {\n    const dl = document.createElement('dl');\n\n    // Kongtext font button\n    const dtKongtext = document.createElement('dt');\n    dtKongtext.textContent = 'Kongtext';\n    dl.appendChild(dtKongtext);\n\n    const ddKongtext = document.createElement('dd');\n    const btnKongtext = document.createElement('button');\n    btnKongtext.textContent = 'Load Kongtext';\n    btnKongtext.title = 'Load Kongtext font and apply C64 style';\n    btnKongtext.addEventListener('click', async () => {\n      const ff = new FontFace('Kongtext', 'url(/fonts/kongtext.regular.ttf) format(\\'truetype\\')');\n      await loadFonts([ff]);\n      this._terminal.options.fontFamily = 'Kongtext';\n      this._terminal.options.lineHeight = 1.3;\n      this._addons.fit.instance?.fit();\n      setTimeout(() => this._terminal.write('\\x1b[?12h\\x1b]12;#776CF9\\x07\\x1b[38;2;119;108;249;48;2;21;8;150m\\x1b[2J\\x1b[2;5H**** COMMODORE 64 BASIC V2 ****\\r\\n\\r\\n 64K RAM SYSTEM  38911 BASIC BYTES FREE\\r\\n\\r\\nREADY.\\r\\nLOAD '), 1000);\n      setTimeout(() => { this._terminal.write('🤣\\x1b[m\\x1b[99;1H'); this._terminal.input('\\r'); }, 5000);\n    });\n    ddKongtext.appendChild(btnKongtext);\n    dl.appendChild(ddKongtext);\n\n    // BPdots font button\n    const dtBpdots = document.createElement('dt');\n    dtBpdots.textContent = 'BPdots';\n    dl.appendChild(dtBpdots);\n\n    const ddBpdots = document.createElement('dd');\n    const btnBpdots = document.createElement('button');\n    btnBpdots.textContent = 'Load BPdots';\n    btnBpdots.title = 'Load BPdots font';\n    btnBpdots.addEventListener('click', async () => {\n      document.styleSheets[0].insertRule('@font-face { font-family: \"BPdots\"; src: url(/fonts/bpdots.regular.otf) format(\"opentype\"); weight: 400 }', 0);\n      await loadFonts(['BPdots']);\n      this._terminal.options.fontFamily = 'BPdots';\n      this._terminal.options.lineHeight = 1.3;\n      this._terminal.options.fontSize = 20;\n      this._addons.fit.instance?.fit();\n    });\n    ddBpdots.appendChild(btnBpdots);\n    dl.appendChild(ddBpdots);\n\n    container.appendChild(dl);\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonWebLinksWindow.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class AddonWebLinksWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-web-links';\n  public readonly label = 'web-links';\n\n  public build(container: HTMLElement): void {\n    const dl = document.createElement('dl');\n    const dt = document.createElement('dt');\n    dt.textContent = 'Weblinks Addon';\n    dl.appendChild(dt);\n\n    const dd = document.createElement('dd');\n    const button = document.createElement('button');\n    button.id = 'weblinks-test';\n    button.textContent = 'Test URLs';\n    button.title = 'Various url conditions from demo data, hover&click to test';\n    button.addEventListener('click', () => this._testWeblinks());\n    dd.appendChild(button);\n    dl.appendChild(dd);\n\n    container.appendChild(dl);\n  }\n\n  private _testWeblinks(): void {\n    const linkExamples = `\n  aaa http://example.com aaa http://example.com aaa\n  \\uFFE5\\uFFE5\\uFFE5 http://example.com aaa http://example.com aaa\n  aaa http://example.com \\uFFE5\\uFFE5\\uFFE5 http://example.com aaa\n  \\uFFE5\\uFFE5\\uFFE5 http://example.com \\uFFE5\\uFFE5\\uFFE5 http://example.com aaa\n  aaa https://ko.wikipedia.org/wiki/\\uC704\\uD0A4\\uBC31\\uACFC:\\uB300\\uBB38 aaa https://ko.wikipedia.org/wiki/\\uC704\\uD0A4\\uBC31\\uACFC:\\uB300\\uBB38 aaa\n  \\uFFE5\\uFFE5\\uFFE5 https://ko.wikipedia.org/wiki/\\uC704\\uD0A4\\uBC31\\uACFC:\\uB300\\uBB38 aaa https://ko.wikipedia.org/wiki/\\uC704\\uD0A4\\uBC31\\uACFC:\\uB300\\uBB38 \\uFFE5\\uFFE5\\uFFE5\n  aaa http://test:password@example.com/some_path aaa\n  brackets enclosed:\n  aaa [http://example.de] aaa\n  aaa (http://example.de) aaa\n  aaa <http://example.de> aaa\n  aaa {http://example.de} aaa\n  ipv6 https://[::1]/with/some?vars=and&a#hash aaa\n  stop at final '.': This is a sentence with an url to http://example.com.\n  stop at final '?': Is this the right url http://example.com/?\n  stop at final '?': Maybe this one http://example.com/with?arguments=false?\n  `;\n    this._terminal.write(linkExamples.split('\\n').join('\\r\\n'));\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/addonsWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class AddonsWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addons';\n  public readonly label = 'Addons';\n\n  private _addonsContainer!: HTMLElement;\n\n  public build(container: HTMLElement): void {\n    // Description\n    const description = document.createElement('p');\n    description.textContent = 'Addons can be loaded and unloaded on a particular terminal to extend its functionality.';\n    container.appendChild(description);\n\n    // Addons container (checkboxes go here)\n    this._addonsContainer = document.createElement('div');\n    this._addonsContainer.id = 'addons-container';\n    container.appendChild(this._addonsContainer);\n  }\n\n  public get addonsContainer(): HTMLElement {\n    return this._addonsContainer;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/baseWindow.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { AddonCollection } from '../../types';\nimport type { IControlWindow } from '../controlBar';\nimport type { Terminal } from '@xterm/xterm';\n\nexport abstract class BaseWindow implements IControlWindow {\n  protected get _terminal(): Terminal { return this._terminalPrivate; }\n\n  constructor(\n    private _terminalPrivate: Terminal,\n    protected readonly _addons: AddonCollection,\n  ) {\n\n  }\n\n  public setTerminal(terminal: Terminal): void {\n    this._terminalPrivate = terminal;\n  }\n\n  public abstract id: string;\n  public abstract label: string;\n  public abstract build(container: HTMLElement): void;\n}\n"
  },
  {
    "path": "demo/client/components/window/cellInspectorWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\nimport type { IBufferCell } from '@xterm/xterm';\n\n// Underline style values from common/buffer/Constants.ts\nconst enum UnderlineStyle {\n  NONE = 0,\n  SINGLE = 1,\n  DOUBLE = 2,\n  CURLY = 3,\n  DOTTED = 4,\n  DASHED = 5\n}\n\n// Internal interface for accessing extended attribute data\ninterface IAttributeDataInternal {\n  getUnderlineStyle(): number;\n  getUnderlineColor(): number;\n  isUnderlineColorDefault(): boolean;\n  isUnderlineColorPalette(): boolean;\n  isUnderlineColorRGB(): boolean;\n}\n\nexport class CellInspectorWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'cell-inspector';\n  public readonly label = 'Cell Inspector';\n\n  private _container!: HTMLElement;\n  private _positionEl!: HTMLElement;\n  private _charEl!: HTMLElement;\n  private _codeEl!: HTMLElement;\n  private _widthEl!: HTMLElement;\n  private _fgEl!: HTMLElement;\n  private _bgEl!: HTMLElement;\n  private _attrsEl!: HTMLElement;\n\n  public build(container: HTMLElement): void {\n    this._container = container;\n\n    const dl = document.createElement('dl');\n    dl.style.fontFamily = 'monospace';\n    dl.style.fontSize = '12px';\n\n    this._positionEl = this._addRow(dl, 'Position');\n    this._charEl = this._addRow(dl, 'Character');\n    this._codeEl = this._addRow(dl, 'Codepoint');\n    this._widthEl = this._addRow(dl, 'Width');\n    this._fgEl = this._addRow(dl, 'Foreground');\n    this._bgEl = this._addRow(dl, 'Background');\n    this._attrsEl = this._addRow(dl, 'Attributes');\n\n    container.appendChild(dl);\n\n    // Add mouse move listener\n    this._setupMouseListener();\n  }\n\n  private _addRow(dl: HTMLElement, label: string): HTMLElement {\n    const dt = document.createElement('dt');\n    dt.textContent = label;\n    dt.style.fontWeight = 'bold';\n    dt.style.marginTop = '4px';\n    dl.appendChild(dt);\n\n    const dd = document.createElement('dd');\n    dd.textContent = '-';\n    dd.style.margin = '0 0 0 16px';\n    dl.appendChild(dd);\n\n    return dd;\n  }\n\n  private _setupMouseListener(): void {\n    const terminal = this._terminal;\n    if (!terminal.element) {\n      return;\n    }\n\n    const cell = terminal.buffer.active.getNullCell();\n\n    terminal.element.addEventListener('mousemove', (e: MouseEvent) => {\n      const core = (terminal as any)._core;\n      const coords = core._mouseCoordsService?.getCoords(e, core.screenElement, terminal.cols, terminal.rows);\n      if (!coords) {\n        this._clearDisplay();\n        return;\n      }\n\n      const x = coords[0] - 1;\n      const y = coords[1] - 1;\n      const bufferY = terminal.buffer.active.viewportY + y;\n\n      const line = terminal.buffer.active.getLine(bufferY);\n      if (!line) {\n        this._clearDisplay();\n        return;\n      }\n\n      const cellData = line.getCell(x, cell);\n      if (!cellData) {\n        this._clearDisplay();\n        return;\n      }\n\n      this._updateDisplay(x, y, bufferY, cellData);\n    });\n\n    terminal.element.addEventListener('mouseleave', () => {\n      this._clearDisplay();\n    });\n  }\n\n  private _clearDisplay(): void {\n    this._positionEl.textContent = '-';\n    this._charEl.textContent = '-';\n    this._codeEl.textContent = '-';\n    this._widthEl.textContent = '-';\n    this._fgEl.textContent = '-';\n    this._bgEl.textContent = '-';\n    this._attrsEl.textContent = '-';\n  }\n\n  private _updateDisplay(x: number, y: number, bufferY: number, cell: IBufferCell): void {\n    // Position\n    this._positionEl.textContent = `x=${x}, y=${y} (buffer: ${bufferY})`;\n\n    // Character\n    const chars = cell.getChars();\n    this._charEl.textContent = chars.length > 0 ? `\"${chars}\"` : '(empty)';\n\n    // Codepoint(s)\n    if (chars.length > 0) {\n      const codepoints = [...chars].map(c => {\n        const cp = c.codePointAt(0)!;\n        return `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;\n      });\n      this._codeEl.textContent = codepoints.join(' ');\n    } else {\n      this._codeEl.textContent = '(none)';\n    }\n\n    // Width\n    this._widthEl.textContent = cell.getWidth().toString();\n\n    // Foreground color\n    this._fgEl.textContent = this._formatFgColor(\n      cell.getFgColor(),\n      cell.isFgDefault(),\n      cell.isFgPalette(),\n      cell.isFgRGB()\n    );\n\n    // Background color\n    this._bgEl.textContent = this._formatBgColor(\n      cell.getBgColor(),\n      cell.isBgDefault(),\n      cell.isBgPalette(),\n      cell.isBgRGB()\n    );\n\n    // Attributes (SGR codes)\n    const attrs: string[] = [];\n    if (cell.isBold()) attrs.push('bold (1)');\n    if (cell.isDim()) attrs.push('dim (2)');\n    if (cell.isItalic()) attrs.push('italic (3)');\n    if (cell.isUnderline()) {\n      const cellData = cell as unknown as IAttributeDataInternal;\n      const style = cellData.getUnderlineStyle();\n      const styleName = this._getUnderlineStyleName(style);\n      attrs.push(`underline ${styleName} (4:${style})`);\n\n      // Show underline color if not default\n      if (!cellData.isUnderlineColorDefault()) {\n        const color = cellData.getUnderlineColor();\n        const colorStr = this._formatUnderlineColor(\n          color,\n          cellData.isUnderlineColorPalette(),\n          cellData.isUnderlineColorRGB()\n        );\n        attrs.push(`underline color: ${colorStr}`);\n      }\n    }\n    if (cell.isBlink()) attrs.push('blink (5)');\n    if (cell.isInverse()) attrs.push('inverse (7)');\n    if (cell.isInvisible()) attrs.push('invisible (8)');\n    if (cell.isStrikethrough()) attrs.push('strikethrough (9)');\n    if (cell.isOverline()) attrs.push('overline (53)');\n    this._attrsEl.textContent = attrs.length > 0 ? attrs.join(', ') : '(none)';\n  }\n\n  private _formatFgColor(color: number, isDefault: boolean, isPalette: boolean, isRGB: boolean): string {\n    if (isDefault) {\n      return 'default (39)';\n    }\n    if (isPalette) {\n      // SGR 30-37 for colors 0-7, 90-97 for colors 8-15, or 38;5;n for 0-255\n      let sgr: string;\n      if (color < 8) {\n        sgr = `${30 + color}`;\n      } else if (color < 16) {\n        sgr = `${90 + color - 8}`;\n      } else {\n        sgr = `38;5;${color}`;\n      }\n      return `palette(${color}) (${sgr})`;\n    }\n    if (isRGB) {\n      const r = (color >> 16) & 0xFF;\n      const g = (color >> 8) & 0xFF;\n      const b = color & 0xFF;\n      return `#${color.toString(16).toUpperCase().padStart(6, '0')} (38;2;${r};${g};${b})`;\n    }\n    return `unknown(${color})`;\n  }\n\n  private _formatBgColor(color: number, isDefault: boolean, isPalette: boolean, isRGB: boolean): string {\n    if (isDefault) {\n      return 'default (49)';\n    }\n    if (isPalette) {\n      // SGR 40-47 for colors 0-7, 100-107 for colors 8-15, or 48;5;n for 0-255\n      let sgr: string;\n      if (color < 8) {\n        sgr = `${40 + color}`;\n      } else if (color < 16) {\n        sgr = `${100 + color - 8}`;\n      } else {\n        sgr = `48;5;${color}`;\n      }\n      return `palette(${color}) (${sgr})`;\n    }\n    if (isRGB) {\n      const r = (color >> 16) & 0xFF;\n      const g = (color >> 8) & 0xFF;\n      const b = color & 0xFF;\n      return `#${color.toString(16).toUpperCase().padStart(6, '0')} (48;2;${r};${g};${b})`;\n    }\n    return `unknown(${color})`;\n  }\n\n  private _getUnderlineStyleName(style: UnderlineStyle): string {\n    switch (style) {\n      case UnderlineStyle.NONE: return 'none';\n      case UnderlineStyle.SINGLE: return 'single';\n      case UnderlineStyle.DOUBLE: return 'double';\n      case UnderlineStyle.CURLY: return 'curly';\n      case UnderlineStyle.DOTTED: return 'dotted';\n      case UnderlineStyle.DASHED: return 'dashed';\n      default: return 'unknown';\n    }\n  }\n\n  private _formatUnderlineColor(color: number, isPalette: boolean, isRGB: boolean): string {\n    if (isPalette) {\n      return `palette(${color}) (58;5;${color})`;\n    }\n    if (isRGB) {\n      const r = (color >> 16) & 0xFF;\n      const g = (color >> 8) & 0xFF;\n      const b = color & 0xFF;\n      return `#${color.toString(16).toUpperCase().padStart(6, '0')} (58;2;${r};${g};${b})`;\n    }\n    return `unknown(${color})`;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/gpuWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class GpuWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'gpu';\n  public readonly label = 'WebGL';\n\n  private _textureAtlasContainer!: HTMLElement;\n\n  public build(container: HTMLElement): void {\n    const zoomCheckbox = document.createElement('input');\n    zoomCheckbox.type = 'checkbox';\n    zoomCheckbox.id = 'texture-atlas-zoom';\n    container.appendChild(zoomCheckbox);\n\n    const zoomLabel = document.createElement('label');\n    zoomLabel.htmlFor = 'texture-atlas-zoom';\n    zoomLabel.textContent = 'Zoom texture atlas';\n    container.appendChild(zoomLabel);\n\n    this._textureAtlasContainer = document.createElement('div');\n    this._textureAtlasContainer.id = 'texture-atlas';\n    container.appendChild(this._textureAtlasContainer);\n  }\n\n  public setTextureAtlas(canvas: HTMLCanvasElement): void {\n    this._styleAtlasPage(canvas);\n    this._textureAtlasContainer.replaceChildren(canvas);\n  }\n\n  public appendTextureAtlas(canvas: HTMLCanvasElement): void {\n    this._styleAtlasPage(canvas);\n    this._textureAtlasContainer.appendChild(canvas);\n  }\n\n  public removeTextureAtlas(canvas: HTMLCanvasElement): void {\n    canvas.remove();\n  }\n\n  private _styleAtlasPage(canvas: HTMLCanvasElement): void {\n    // eslint-disable-next-line no-restricted-syntax\n    const dpr = window.devicePixelRatio;\n    canvas.style.width = `${canvas.width / dpr}px`;\n    canvas.style.height = `${canvas.height / dpr}px`;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/optionsWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { Terminal, ITheme } from '@xterm/xterm';\nimport type { IControlWindow } from '../controlBar';\nimport type { AddonCollection } from 'types';\n\nconst xtermjsTheme: ITheme = Object.freeze({\n  foreground: '#F8F8F8',\n  background: '#2D2E2C',\n  selectionBackground: '#5DA5D533',\n  selectionInactiveBackground: '#555555AA',\n  black: '#1E1E1D',\n  brightBlack: '#262625',\n  red: '#CE5C5C',\n  brightRed: '#FF7272',\n  green: '#5BCC5B',\n  brightGreen: '#72FF72',\n  yellow: '#CCCC5B',\n  brightYellow: '#FFFF72',\n  blue: '#5D5DD3',\n  brightBlue: '#7279FF',\n  magenta: '#BC5ED1',\n  brightMagenta: '#E572FF',\n  cyan: '#5DA5D5',\n  brightCyan: '#72F0FF',\n  white: '#F8F8F8',\n  brightWhite: '#FFFFFF'\n});\nconst sapphireTheme: ITheme = Object.freeze({\n  background: '#1c2431',\n  foreground: '#cccccc',\n  selectionBackground: '#399ef440',\n  black: '#666666',\n  blue: '#399ef4',\n  brightBlack: '#666666',\n  brightBlue: '#399ef4',\n  brightCyan: '#21c5c7',\n  brightGreen: '#4eb071',\n  brightMagenta: '#b168df',\n  brightRed: '#da6771',\n  brightWhite: '#efefef',\n  brightYellow: '#fff099',\n  cyan: '#21c5c7',\n  green: '#4eb071',\n  magenta: '#b168df',\n  red: '#da6771',\n  white: '#efefef',\n  yellow: '#fff099'\n});\nconst lightTheme: ITheme = Object.freeze({\n  background: '#ffffff',\n  foreground: '#333333',\n  cursor: '#333333',\n  cursorAccent: '#ffffff',\n  selectionBackground: '#add6ff',\n  overviewRulerBorder: '#aaaaaa',\n  black: '#000000',\n  blue: '#0451a5',\n  brightBlack: '#666666',\n  brightBlue: '#0451a5',\n  brightCyan: '#0598bc',\n  brightGreen: '#14ce14',\n  brightMagenta: '#bc05bc',\n  brightRed: '#cd3131',\n  brightWhite: '#a5a5a5',\n  brightYellow: '#b5ba00',\n  cyan: '#0598bc',\n  green: '#00bc00',\n  magenta: '#bc05bc',\n  red: '#cd3131',\n  white: '#555555',\n  yellow: '#949800'\n});\n\nexport class OptionsWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'options';\n  public readonly label = 'Options';\n\n  private _container!: HTMLElement;\n  private _optionsContainer!: HTMLElement;\n  private _autoResize: boolean = true;\n\n  constructor(\n    terminal: Terminal,\n    addons: AddonCollection,\n    private readonly _handlers: {\n      updateTerminalSize: () => void;\n      updateTerminalContainerBackground: () => void;\n    },\n  ) {\n    super(terminal, addons);\n  }\n\n  public build(container: HTMLElement): void {\n    this._container = container;\n\n    const description = document.createElement('p');\n    description.innerHTML = 'These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.options</code> property.';\n    container.appendChild(description);\n\n    this._optionsContainer = document.createElement('div');\n    this._optionsContainer.id = 'options-container';\n    container.appendChild(this._optionsContainer);\n  }\n\n  public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {\n    const blacklistedOptions = [\n      'convertEol',\n      'termName',\n      'cols', 'rows',\n      'documentOverride',\n      'linkHandler',\n      'logger',\n      'quirks',\n      'scrollbar',\n      'theme',\n      'vtExtensions',\n      'windowOptions',\n      'windowsPty',\n    ];\n    const nestedBooleanOptions: { label: string, path: string[], prop: string }[] = [\n      { label: 'scrollbar.showScrollbar', path: ['scrollbar'], prop: 'showScrollbar' },\n      { label: 'scrollbar.showArrows', path: ['scrollbar'], prop: 'showArrows' },\n      { label: 'scrollbar.overviewRuler.showTopBorder', path: ['scrollbar', 'overviewRuler'], prop: 'showTopBorder' },\n      { label: 'scrollbar.overviewRuler.showBottomBorder', path: ['scrollbar', 'overviewRuler'], prop: 'showBottomBorder' },\n      { label: 'vtExtensions.kittyKeyboard', path: ['vtExtensions'], prop: 'kittyKeyboard' },\n      { label: 'vtExtensions.kittySgrBoldFaintControl', path: ['vtExtensions'], prop: 'kittySgrBoldFaintControl' },\n      { label: 'vtExtensions.win32InputMode', path: ['vtExtensions'], prop: 'win32InputMode' }\n    ];\n    const stringOptions: { [key: string]: string[] | null } = {\n      cursorStyle: ['block', 'underline', 'bar'],\n      cursorInactiveStyle: ['outline', 'block', 'bar', 'underline', 'none'],\n      fontFamily: null,\n      fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],\n      fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],\n      logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'],\n      theme: ['default', 'xtermjs', 'sapphire', 'light'],\n      wordSeparator: null,\n      colsRows: null\n    };\n    const options = Object.getOwnPropertyNames(this._terminal.options);\n    const booleanOptions: string[] = [];\n    const numberOptions: string[] = [];\n    options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {\n      switch (typeof (this._terminal.options as Record<string, unknown>)[o]) {\n        case 'boolean':\n          booleanOptions.push(o);\n          break;\n        case 'number':\n          numberOptions.push(o);\n          break;\n        default:\n          if (Object.keys(stringOptions).indexOf(o) === -1 && numberOptions.indexOf(o) === -1 && booleanOptions.indexOf(o) === -1) {\n            console.warn(`Unrecognized option: \"${o}\"`);\n          }\n      }\n    });\n\n    let html = '';\n    html += '<div class=\"option-group\">';\n    booleanOptions.forEach(o => {\n      html += `<div class=\"option\"><label><input id=\"opt-${o}\" type=\"checkbox\" ${(this._terminal.options as Record<string, unknown>)[o] ? 'checked' : ''}/> ${o}</label></div>`;\n    });\n    nestedBooleanOptions.forEach(({ label, path, prop }) => {\n      const options = this._terminal.options as Record<string, unknown>;\n      const parent = path.reduce<Record<string, unknown> | undefined>((acc, key) => (acc as Record<string, unknown> | undefined)?.[key] as Record<string, unknown> | undefined, options);\n      const checked = (parent as Record<string, unknown> | undefined)?.[prop] ?? false;\n      html += `<div class=\"option\"><label><input id=\"opt-${label.replace('.', '-')}\" type=\"checkbox\" ${checked ? 'checked' : ''}/> ${label}</label></div>`;\n    });\n    html += '</div><div class=\"option-group\">';\n    numberOptions.forEach(o => {\n      html += `<div class=\"option\"><label>${o} <input id=\"opt-${o}\" type=\"number\" value=\"${(this._terminal.options as Record<string, unknown>)[o] ?? ''}\" step=\"${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}\"/></label></div>`;\n    });\n    html += '</div><div class=\"option-group\">';\n    Object.keys(stringOptions).forEach(o => {\n      if (o === 'colsRows') {\n        html += `<div class=\"option\"><label>size (<var>cols</var><code>x</code><var>rows</var> or <code>auto</code>) <input id=\"opt-${o}\" type=\"text\" value=\"auto\"/></label></div>`;\n      } else if (stringOptions[o]) {\n        const selectedOption = o === 'theme' ? 'xtermjs' : (this._terminal.options as Record<string, unknown>)[o];\n        html += `<div class=\"option\"><label>${o} <select id=\"opt-${o}\">${stringOptions[o]!.map(v => `<option ${v === selectedOption ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;\n      } else {\n        html += `<div class=\"option\"><label>${o} <input id=\"opt-${o}\" type=\"text\" value=\"${(this._terminal.options as Record<string, unknown>)[o]}\"/></label></div>`;\n      }\n    });\n    html += '</div>';\n\n    this._optionsContainer.innerHTML = html;\n\n    // Attach listeners\n    booleanOptions.forEach(o => {\n      const input = document.getElementById(`opt-${o}`) as HTMLInputElement;\n      addDomListener(input, 'change', () => {\n        console.log('change', o, input.checked);\n        (this._terminal.options as Record<string, unknown>)[o] = input.checked;\n        if (o ==='allowTransparency') {\n          this._terminal.options.theme = this._getTheme();\n          this._handlers.updateTerminalContainerBackground();\n        }\n      });\n    });\n    nestedBooleanOptions.forEach(({ label, path, prop }) => {\n      const input = document.getElementById(`opt-${label.replace('.', '-')}`) as HTMLInputElement;\n      addDomListener(input, 'change', () => {\n        console.log('change', label, input.checked);\n        const options = this._terminal.options as Record<string, unknown>;\n        if (path.length === 1) {\n          const parentKey = path[0];\n          options[parentKey] = { ...(options[parentKey] as Record<string, unknown> | undefined), [prop]: input.checked };\n          return;\n        }\n        if (path.length === 2) {\n          const [parentKey, childKey] = path;\n          const parent = (options[parentKey] as Record<string, unknown> | undefined) ?? {};\n          const child = (parent[childKey] as Record<string, unknown> | undefined) ?? {};\n          options[parentKey] = { ...parent, [childKey]: { ...child, [prop]: input.checked } };\n        }\n      });\n    });\n    numberOptions.forEach(o => {\n      const input = document.getElementById(`opt-${o}`) as HTMLInputElement;\n      addDomListener(input, 'change', () => {\n        console.log('change', o, input.value);\n        if (o === 'lineHeight') {\n          this._terminal.options.lineHeight = parseFloat(input.value);\n        } else if (o === 'scrollSensitivity') {\n          this._terminal.options.scrollSensitivity = parseFloat(input.value);\n        } else if (o === 'scrollback') {\n          this._terminal.options.scrollback = parseInt(input.value);\n          setTimeout(() => this._handlers.updateTerminalSize(), 5);\n        } else {\n          (this._terminal.options as Record<string, unknown>)[o] = parseInt(input.value);\n        }\n        this._handlers.updateTerminalSize();\n      });\n    });\n    Object.keys(stringOptions).forEach(o => {\n      const input = document.getElementById(`opt-${o}`) as HTMLInputElement;\n      addDomListener(input, 'change', () => {\n        console.log('change', o, input.value);\n        let value: unknown = input.value;\n        if (o === 'colsRows') {\n          const m = input.value.match(/^([0-9]+)x([0-9]+)$/);\n          if (m) {\n            this._autoResize = false;\n            this._terminal.resize(parseInt(m[1]), parseInt(m[2]));\n          } else {\n            this._autoResize = true;\n            input.value = 'auto';\n            this._handlers.updateTerminalSize();\n          }\n        } else if (o === 'theme') {\n          value = this._getTheme();\n        }\n        (this._terminal.options as Record<string, unknown>)[o] = value;\n        if (o === 'theme') {\n          this._handlers.updateTerminalContainerBackground();\n        }\n        if (o === 'fontFamily') {\n          this._handlers.updateTerminalSize();\n        }\n      });\n    });\n  }\n\n  public get autoResize(): boolean {\n    return this._autoResize;\n  }\n\n  public set autoResize(value: boolean) {\n    this._autoResize = value;\n  }\n\n  private _getTheme(): ITheme {\n    const input = document.querySelector<HTMLInputElement>('#opt-theme')!;\n    let theme: ITheme = {};\n    switch (input.value) {\n      case 'default':\n        theme = {};\n        break;\n      case 'xtermjs':\n        theme = { ...xtermjsTheme };\n        break;\n      case 'sapphire':\n        theme = { ...sapphireTheme };\n        break;\n      case 'light':\n        theme = { ...lightTheme };\n        break;\n    }\n    if (this._terminal.options.allowTransparency) {\n      theme.background = 'transparent';\n    }\n    return theme;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/styleWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class StyleWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'style';\n  public readonly label = 'Style';\n\n  private _paddingElement!: HTMLInputElement;\n\n  public build(container: HTMLElement): void {\n    const wrapper = document.createElement('div');\n    wrapper.style.display = 'inline-block';\n    wrapper.style.marginRight = '16px';\n\n    const label = document.createElement('label');\n    label.htmlFor = 'padding';\n    label.textContent = 'Padding';\n    wrapper.appendChild(label);\n\n    this._paddingElement = document.createElement('input');\n    this._paddingElement.type = 'number';\n    this._paddingElement.id = 'padding';\n    wrapper.appendChild(this._paddingElement);\n\n    container.appendChild(wrapper);\n  }\n\n  public get paddingElement(): HTMLInputElement {\n    return this._paddingElement;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/testWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { writeUnicodeTable } from '../../unicodeTable';\nimport type { IControlWindow } from '../controlBar';\nimport { BaseWindow } from './baseWindow';\nimport type { IDisposable, Terminal } from '@xterm/xterm';\nimport type { AddonCollection } from 'types';\n\nexport class TestWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'test';\n  public readonly label = 'Test';\n\n  constructor(\n    terminal: Terminal,\n    addons: AddonCollection,\n    private readonly _handlers: {\n      disposeRecreateButtonHandler: () => void;\n      createNewWindowButtonHandler: () => void;\n    },\n  ) {\n    super(terminal, addons);\n  }\n\n  public build(container: HTMLElement): void {\n    const wrapper = document.createElement('div');\n    wrapper.style.display = 'inline-block';\n    wrapper.style.marginRight = '16px';\n\n    const dl = document.createElement('dl');\n\n    // Lifecycle section\n    this._addDt(dl, 'Lifecycle');\n    this._addDdWithCheckbox(dl, 'use-real-terminal', 'Use real terminal', 'This is used to real vs fake terminals', true);\n    this._addDdWithButton(dl, 'dispose', 'Dispose terminal', 'This is used to testing memory leaks', () => this._handlers.disposeRecreateButtonHandler());\n    this._addDdWithButton(dl, 'create-new-window', 'Create terminal in new window', 'This is used to test rendering in other windows', () => this._handlers.createNewWindowButtonHandler());\n\n    // Performance section\n    this._addDt(dl, 'Performance');\n    this._addDdWithButton(dl, 'load-test', 'Load test', 'Write several MB of data to simulate a lot of data coming from the process', () => loadTest(this._terminal, this._addons));\n    this._addDdWithButton(dl, 'load-test-long-lines', 'Load test (long lines)', 'Write several MB of data with long lines to simulate a lot of data coming from the process', () => loadTestLongLines(this._terminal, this._addons));\n    this._addDdWithButton(dl, 'print-cjk', 'CJK Unified Ideographs', 'Prints the 20977 characters from the CJK Unified Ideographs unicode block', () => addCjk(this._terminal));\n    this._addDdWithButton(dl, 'print-cjk-sgr', 'CJK Unified Ideographs (random SGR)', 'Prints the 20977 characters from the CJK Unified Ideographs unicode block with randomized SGR attributes', () => addCjkRandomSgr(this._terminal));\n\n    // Styles section\n    this._addDt(dl, 'Styles');\n    this._addDdWithButton(dl, 'custom-glyph-alignment', 'Custom glyph alignment test', 'Write custom glyph alignment tests to the terminal', () => customGlyphAlignmentHandler(this._terminal));\n    this._addDdWithButton(dl, 'custom-glyph-ranges', 'Custom glyph ranges', 'Write custom glyph unicode range to the terminal', () => customGlyphRangesHandler(this._terminal));\n    this._addDdWithButton(dl, 'powerline-symbol-test', 'Powerline symbol test', 'Write powerline symbol characters to the terminal (\\\\ue0a0+)', () => powerlineSymbolTest(this._terminal));\n    this._addDdWithButton(dl, 'nerd-font-icons', 'Nerd Font icons', 'Write all Nerd Font icon ranges to the terminal', () => nerdFontIconsTest(this._terminal));\n    this._addDdWithButton(dl, 'underline-test', 'Underline test', 'Write text with Kitty\\'s extended underline sequences', () => underlineTest(this._terminal));\n    this._addDdWithButton(dl, 'sgr-test', 'SGR test', 'Write text with SGR attribute', () => sgrTest(this._terminal));\n    this._addDdWithButton(dl, 'ansi-colors', 'Ansi colors test', 'Write a wide range of ansi colors', () => ansiColorsTest(this._terminal));\n    this._addDdWithButton(dl, 'osc-hyperlinks', 'Ansi hyperlinks test', 'Write some OSC 8 hyperlinks', () => addAnsiHyperlink(this._terminal));\n    this._addDdWithButton(dl, 'bce', 'Colored Erase (BCE)', 'Test colored erase', () => coloredErase(this._terminal));\n    this._addDdWithButton(dl, 'add-grapheme-clusters', 'Grapheme clusters', 'Write grapheme cluster test strings', () => addGraphemeClusters(this._terminal));\n\n    // Decorations section\n    this._addDt(dl, 'Decorations');\n    this._addDdWithButton(dl, 'add-decoration', 'Decoration (1x1)', 'Add a 1x1 decoration to the terminal', () => addDecoration(this._terminal));\n    this._addDdWithButton(dl, 'add-decoration', 'Decoration (3x3)', 'Add a 3x3 decoration to the terminal', () => addDecoration(this._terminal, 3));\n    this._addDdWithButton(dl, 'add-overview-ruler', 'Add Overview Ruler', 'Add an overview ruler to the terminal', () => addOverviewRuler(this._terminal));\n    this._addDdWithButton(dl, 'decoration-stress-test', 'Stress Test', 'Toggle between adding and removing a decoration to each line', () => decorationStressTest(this._terminal));\n\n    // Events Test section\n    this._addDt(dl, 'Events Test');\n    this._addDdWithButton(dl, 'event-focus', 'focus', '', () => this._terminal.focus());\n    this._addDdWithButton(dl, 'event-blur', 'blur', '', () => this._terminal.blur());\n\n    wrapper.appendChild(dl);\n    container.appendChild(wrapper);\n  }\n\n  private _addDt(dl: HTMLElement, text: string): void {\n    const dt = document.createElement('dt');\n    dt.textContent = text;\n    dl.appendChild(dt);\n  }\n\n  private _addDdWithButton(dl: HTMLElement, id: string, label: string, title: string, handler?: () => void): void {\n    const dd = document.createElement('dd');\n    const button = document.createElement('button');\n    button.id = id;\n    button.textContent = label;\n    if (title) {\n      button.title = title;\n    }\n    dd.appendChild(button);\n    dl.appendChild(dd);\n    if (handler) {\n      button.addEventListener('click', handler);\n    }\n  }\n\n  private _addDdWithCheckbox(dl: HTMLElement, id: string, label: string, title: string, checked: boolean): void {\n    const dd = document.createElement('dd');\n    const labelElement = document.createElement('label');\n    labelElement.htmlFor = id;\n    const checkbox = document.createElement('input');\n    checkbox.type = 'checkbox';\n    checkbox.id = id;\n    checkbox.checked = checked;\n    if (title) {\n      checkbox.title = title;\n    }\n    labelElement.appendChild(checkbox);\n    labelElement.appendChild(document.createTextNode(label));\n    dd.appendChild(labelElement);\n    dl.appendChild(dd);\n  }\n\n}\n\n/**\n * Prints the 20977 characters from the CJK Unified Ideographs unicode block.\n */\nexport function addCjk(term: Terminal): void {\n  term.write('\\n\\n\\r');\n  for (let i = 0x4E00; i < 0x9FCC; i++) {\n    term.write(String.fromCharCode(i));\n  }\n}\n\n/**\n * Prints the 20977 characters from the CJK Unified Ideographs unicode block with randomized styles.\n */\nfunction addCjkRandomSgr(term: Terminal): void {\n  term.write('\\n\\n\\r');\n  for (let i = 0x4E00; i < 0x9FCC; i++) {\n    term.write(`\\x1b[${getRandomSgr()}m${String.fromCharCode(i)}\\x1b[0m`);\n  }\n}\nconst randomSgrAttributes = [\n  '1', '2', '3', '4', '5', '6', '7', '9',\n  '21', '22', '23', '24', '25', '26', '27', '28', '29',\n  '30', '31', '32', '33', '34', '35', '36', '37', '38', '39',\n  '40', '41', '42', '43', '44', '45', '46', '47', '48', '49'\n];\nfunction getRandomSgr(): string {\n  return randomSgrAttributes[Math.floor(Math.random() * randomSgrAttributes.length)];\n}\n\nfunction powerlineSymbolTest(term: Terminal): void {\n  function s(char: string): string {\n    return `${char} \\x1b[7m${char}\\x1b[0m  `;\n  }\n  term.write('\\n\\n\\r');\n  term.writeln('Standard powerline symbols:');\n  term.writeln('      0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F');\n  term.writeln(`0xA_  ${s('\\ue0a0')}${s('\\ue0a1')}${s('\\ue0a2')}`);\n  term.writeln(`0xB_  ${s('\\ue0b0')}${s('\\ue0b1')}${s('\\ue0b2')}${s('\\ue0b3')}`);\n  term.writeln('');\n  term.writeln(\n    `\\x1b[7m` +\n    ` inverse \\ue0b1 \\x1b[0;40m\\ue0b0` +\n    ` 0 \\ue0b1 \\x1b[30;41m\\ue0b0\\x1b[39m` +\n    ` 1 \\ue0b1 \\x1b[31;42m\\ue0b0\\x1b[39m` +\n    ` 2 \\ue0b1 \\x1b[32;43m\\ue0b0\\x1b[39m` +\n    ` 3 \\ue0b1 \\x1b[33;44m\\ue0b0\\x1b[39m` +\n    ` 4 \\ue0b1 \\x1b[34;45m\\ue0b0\\x1b[39m` +\n    ` 5 \\ue0b1 \\x1b[35;46m\\ue0b0\\x1b[39m` +\n    ` 6 \\ue0b1 \\x1b[36;47m\\ue0b0\\x1b[30m` +\n    ` 7 \\ue0b1 \\x1b[37;49m\\ue0b0\\x1b[0m`\n  );\n  term.writeln('');\n  term.writeln(\n    `\\x1b[7m` +\n    ` inverse \\ue0b3 \\x1b[0;7;40m\\ue0b2\\x1b[27m` +\n    ` 0 \\ue0b3 \\x1b[7;30;41m\\ue0b2\\x1b[27;39m` +\n    ` 1 \\ue0b3 \\x1b[7;31;42m\\ue0b2\\x1b[27;39m` +\n    ` 2 \\ue0b3 \\x1b[7;32;43m\\ue0b2\\x1b[27;39m` +\n    ` 3 \\ue0b3 \\x1b[7;33;44m\\ue0b2\\x1b[27;39m` +\n    ` 4 \\ue0b3 \\x1b[7;34;45m\\ue0b2\\x1b[27;39m` +\n    ` 5 \\ue0b3 \\x1b[7;35;46m\\ue0b2\\x1b[27;39m` +\n    ` 6 \\ue0b3 \\x1b[7;36;47m\\ue0b2\\x1b[27;30m` +\n    ` 7 \\ue0b3 \\x1b[7;37;49m\\ue0b2\\x1b[0m`\n  );\n  term.writeln('');\n  term.writeln(\n    `\\x1b[7m` +\n    ` inverse \\ue0b5 \\x1b[0;40m\\ue0b4` +\n    ` 0 \\ue0b5 \\x1b[30;41m\\ue0b4\\x1b[39m` +\n    ` 1 \\ue0b5 \\x1b[31;42m\\ue0b4\\x1b[39m` +\n    ` 2 \\ue0b5 \\x1b[32;43m\\ue0b4\\x1b[39m` +\n    ` 3 \\ue0b5 \\x1b[33;44m\\ue0b4\\x1b[39m` +\n    ` 4 \\ue0b5 \\x1b[34;45m\\ue0b4\\x1b[39m` +\n    ` 5 \\ue0b5 \\x1b[35;46m\\ue0b4\\x1b[39m` +\n    ` 6 \\ue0b5 \\x1b[36;47m\\ue0b4\\x1b[30m` +\n    ` 7 \\ue0b5 \\x1b[37;49m\\ue0b4\\x1b[0m`\n  );\n  term.writeln('');\n  term.writeln(\n    `\\x1b[7m` +\n    ` inverse \\ue0b7 \\x1b[0;7;40m\\ue0b6\\x1b[27m` +\n    ` 0 \\ue0b7 \\x1b[7;30;41m\\ue0b6\\x1b[27;39m` +\n    ` 1 \\ue0b7 \\x1b[7;31;42m\\ue0b6\\x1b[27;39m` +\n    ` 2 \\ue0b7 \\x1b[7;32;43m\\ue0b6\\x1b[27;39m` +\n    ` 3 \\ue0b7 \\x1b[7;33;44m\\ue0b6\\x1b[27;39m` +\n    ` 4 \\ue0b7 \\x1b[7;34;45m\\ue0b6\\x1b[27;39m` +\n    ` 5 \\ue0b7 \\x1b[7;35;46m\\ue0b6\\x1b[27;39m` +\n    ` 6 \\ue0b7 \\x1b[7;36;47m\\ue0b6\\x1b[27;30m` +\n    ` 7 \\ue0b7 \\x1b[7;37;49m\\ue0b6\\x1b[0m`\n  );\n  term.writeln('');\n  term.writeln('Powerline extra symbols:');\n  term.writeln('      0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F');\n  term.writeln(`0xA_                 ${s('\\ue0a3')}`);\n  term.writeln(`0xB_                      ${s('\\ue0b4')}${s('\\ue0b5')}${s('\\ue0b6')}${s('\\ue0b7')}${s('\\ue0b8')}${s('\\ue0b9')}${s('\\ue0ba')}${s('\\ue0bb')}${s('\\ue0bc')}${s('\\ue0bd')}${s('\\ue0be')}${s('\\ue0bf')}`);\n  term.writeln(`0xC_  ${s('\\ue0c0')}${s('\\ue0c1')}${s('\\ue0c2')}${s('\\ue0c3')}${s('\\ue0c4')}${s('\\ue0c5')}${s('\\ue0c6')}${s('\\ue0c7')}${s('\\ue0c8')}${s('\\ue0c9')}${s('\\ue0ca')}${s('\\ue0cb')}${s('\\ue0cc')}${s('\\ue0cd')}${s('\\ue0be')}${s('\\ue0bf')}`);\n  term.writeln(`0xD_  ${s('\\ue0d0')}${s('\\ue0d1')}${s('\\ue0d2')}     ${s('\\ue0d4')}`);\n  term.writeln('');\n  term.writeln('Sample of nerd fonts icons:');\n  term.writeln('    nf-linux-apple (\\\\uF302) \\uf302');\n  term.writeln('nf-mdi-github_face (\\\\uFbd9) \\ufbd9');\n}\n\nfunction nerdFontIconsTest(term: Terminal): void {\n  term.write('\\n\\n\\r');\n  term.writeln('\\x1b[1mNerd Font Icon Ranges\\x1b[0m');\n  term.writeln('https://github.com/ryanoasis/nerd-fonts/wiki/Glyph-Sets-and-Code-Points\\n\\r');\n  writeUnicodeTable(term, 'Seti-UI + Custom', 0xE5FA, 0xE6B7, [\n    ['Seti-UI + Custom', 0xE5FA, 0xE6B7],\n  ]);\n  writeUnicodeTable(term, 'Devicons', 0xE700, 0xE8EF, [\n    ['Devicons', 0xE700, 0xE8EF],\n  ]);\n  writeUnicodeTable(term, 'Font Awesome', 0xED00, 0xF2FF, [\n    ['Font Awesome', 0xED00, 0xF2FF],\n  ]);\n  writeUnicodeTable(term, 'Font Awesome Extension', 0xE200, 0xE2A9, [\n    ['Font Awesome Extension', 0xE200, 0xE2A9],\n  ]);\n  writeUnicodeTable(term, 'Material Design Icons', 0xF0001, 0xF1AF0, [\n    ['Material Design Icons', 0xF0001, 0xF1AF0],\n  ]);\n  writeUnicodeTable(term, 'Weather', 0xE300, 0xE3E3, [\n    ['Weather', 0xE300, 0xE3E3],\n  ]);\n  writeUnicodeTable(term, 'Octicons', 0xF400, 0xF533, [\n    ['Octicons', 0xF400, 0xF533],\n  ]);\n  writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0A3, [\n    ['Powerline Symbols', 0xE0A0, 0xE0A3],\n  ]);\n  writeUnicodeTable(term, 'Powerline Extra Symbols', 0xE0B0, 0xE0D4, [\n    ['Powerline Extra Symbols', 0xE0B0, 0xE0D4],\n  ]);\n  writeUnicodeTable(term, 'IEC Power Symbols', 0x23FB, 0x23FE, [\n    ['IEC Power Symbols', 0x23FB, 0x23FE],\n  ]);\n  writeUnicodeTable(term, 'Font Logos', 0xF300, 0xF375, [\n    ['Font Logos', 0xF300, 0xF375],\n  ]);\n  writeUnicodeTable(term, 'Pomicons', 0xE000, 0xE00A, [\n    ['Pomicons', 0xE000, 0xE00A],\n  ]);\n  writeUnicodeTable(term, 'Codicons', 0xEA60, 0xEC1E, [\n    ['Codicons', 0xEA60, 0xEC1E],\n  ]);\n  term.writeln('');\n}\n\nfunction underlineTest(term: Terminal): void {\n  function u(style: number): string {\n    return `\\x1b[4:${style}m`;\n  }\n  function c(color: string): string {\n    return `\\x1b[58:${color}m`;\n  }\n  term.write('\\n\\n\\r');\n  term.writeln('Underline styles:');\n  term.writeln('');\n  function showSequence(id: number, name: string): string {\n    let alphabet = '';\n    for (let i = 97; i < 123; i++) {\n      alphabet += String.fromCharCode(i);\n    }\n    let numbers = '';\n    for (let i = 0; i < 10; i++) {\n      numbers += i.toString();\n    }\n    return `${u(id)}4:${id}m - ${name}\\x1b[4:0m`.padEnd(33, ' ') + `${u(id)}${alphabet} ${numbers} 汉语 한국어 👽\\x1b[4:0m`;\n  }\n  term.writeln(showSequence(0, 'No underline'));\n  term.writeln(showSequence(1, 'Straight'));\n  term.writeln(showSequence(2, 'Double'));\n  term.writeln(showSequence(3, 'Curly'));\n  term.writeln(showSequence(4, 'Dotted'));\n  term.writeln(showSequence(5, 'Dashed'));\n  term.writeln('');\n  term.writeln(`Underline colors (256 color mode):`);\n  term.writeln('');\n  for (let i = 0; i < 256; i++) {\n    term.write((i !== 0 ? '\\x1b[0m, ' : '') + u(1 + i % 5) + c('5:' + i) + i);\n  }\n  term.writeln(`\\x1b[0m\\n\\n\\rUnderline colors (true color mode):`);\n  term.writeln('');\n  for (let i = 0; i < 80; i++) {\n    const v = Math.round(i / 79 * 255);\n    term.write(u(1) + c(`2:0:${v}:${v}:${v}`) + (i < 4 ? 'grey'[i] : ' '));\n  }\n  term.write('\\n\\r');\n  for (let i = 0; i < 80; i++) {\n    const v = Math.round(i / 79 * 255);\n    term.write(u(1) + c(`2:0:${v}:${0}:${0}`) + (i < 3 ? 'red'[i] : ' '));\n  }\n  term.write('\\n\\r');\n  for (let i = 0; i < 80; i++) {\n    const v = Math.round(i / 79 * 255);\n    term.write(u(1) + c(`2:0:${0}:${v}:${0}`) + (i < 5 ? 'green'[i] : ' '));\n  }\n  term.write('\\n\\r');\n  for (let i = 0; i < 80; i++) {\n    const v = Math.round(i / 79 * 255);\n    term.write(u(1) + c(`2:0:${0}:${0}:${v}`) + (i < 4 ? 'blue'[i] : ' '));\n  }\n  term.write('\\x1b[0m\\n\\r');\n}\n\nfunction customGlyphAlignmentHandler(term: Terminal): void {\n  term.write('\\n\\r');\n  term.write('\\n\\r');\n  term.write('Box styles:       ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐\\n\\r');\n  term.write('┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤\\n\\r');\n  term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘\\n\\r');\n  term.write('├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐\\n\\r');\n  term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤\\n\\r');\n  term.write('└─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘\\n\\r');\n  term.write('\\n\\r');\n\n  term.write('Other:\\n\\r');\n  term.write('╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈\\n\\r');\n  term.write('│ │  ╳  ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉\\n\\r');\n  term.write('╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋\\n\\r');\n  term.write('\\n\\r');\n\n  term.write('Box drawing alignment tests:\\x1b[31m                                          █\\n\\r');\n  term.write('                                                                      ▉\\n\\r');\n  term.write('  ╔══╦══╗  ┌──┬──┐  ╭──┬──╮  ╭──┬──╮  ┏━━┳━━┓  ┎┒┏┑   ╷  ╻ ┏┯┓ ┌┰┐    ▊ ╱╲╱╲╳╳╳\\n\\r');\n  term.write('  ║┌─╨─┐║  │╔═╧═╗│  │╒═╪═╕│  │╓─╁─╖│  ┃┌─╂─┐┃  ┗╃╄┙  ╶┼╴╺╋╸┠┼┨ ┝╋┥    ▋ ╲╱╲╱╳╳╳\\n\\r');\n  term.write('  ║│╲ ╱│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╿ │┃  ┍╅╆┓   ╵  ╹ ┗┷┛ └┸┘    ▌ ╱╲╱╲╳╳╳\\n\\r');\n  term.write('  ╠╡ ╳ ╞╣  ├╢   ╟┤  ├┼─┼─┼┤  ├╫─╂─╫┤  ┣┿╾┼╼┿┫  ┕┛┖┚     ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\\n\\r');\n  term.write('  ║│╱ ╲│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╽ │┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▎\\n\\r');\n  term.write('  ║└─╥─┘║  │╚═╤═╝│  │╘═╪═╛│  │╙─╀─╜│  ┃└─╂─┘┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▏\\n\\r');\n  term.write('  ╚══╩══╝  └──┴──┘  ╰──┴──╯  ╰──┴──╯  ┗━━┻━━┛           └╌╌┘ ╎ ┗╍╍┛ ┋  ▁▂▃▄▅▆▇█\\n\\r');\n  term.write('\\x1b[0mBox drawing alignment tests:\\x1b[32m                                          █\\n\\r');\n  term.write('                                                                      ▉\\n\\r');\n  term.write('  ╔══╦══╗  ┌──┬──┐  ╭──┬──╮  ╭──┬──╮  ┏━━┳━━┓  ┎┒┏┑   ╷  ╻ ┏┯┓ ┌┰┐    ▊ ╱╲╱╲╳╳╳\\n\\r');\n  term.write('  ║┌─╨─┐║  │╔═╧═╗│  │╒═╪═╕│  │╓─╁─╖│  ┃┌─╂─┐┃  ┗╃╄┙  ╶┼╴╺╋╸┠┼┨ ┝╋┥    ▋ ╲╱╲╱╳╳╳\\n\\r');\n  term.write('  ║│╲ ╱│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╿ │┃  ┍╅╆┓   ╵  ╹ ┗┷┛ └┸┘    ▌ ╱╲╱╲╳╳╳\\n\\r');\n  term.write('  ╠╡ ╳ ╞╣  ├╢   ╟┤  ├┼─┼─┼┤  ├╫─╂─╫┤  ┣┿╾┼╼┿┫  ┕┛┖┚     ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\\n\\r');\n  term.write('  ║│╱ ╲│║  │║   ║│  ││ │ ││  │║ ┃ ║│  ┃│ ╽ │┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▎\\n\\r');\n  term.write('  ║└─╥─┘║  │╚═╤═╝│  │╘═╪═╛│  │╙─╀─╜│  ┃└─╂─┘┃  ░░▒▒▓▓██ ┊  ┆ ╎ ╏  ┇ ┋ ▏\\n\\r');\n  term.write('  ╚══╩══╝  └──┴──┘  ╰──┴──╯  ╰──┴──╯  ┗━━┻━━┛           └╌╌┘ ╎ ┗╍╍┛ ┋  ▁▂▃▄▅▆▇█\\n\\r');\n\n  term.write('\\x1b[0mSmooth mosaic terminal graphic characters alignment tests:\\x1b[33m\\n\\r');\n  term.write('  🭇🬼 🭈🬽 🭉🬾 🭊🬿 🭋🭀 🭁🭌 🭂🭍 🭃🭎 🭄🭏 🭅🭐 🭆🭑 🭨🭪 🭩 🭯 🭮🭬\\n\\r');\n  term.write('  🭢🭗 🭣🭘 🭤🭙 🭥🭚 🭦🭛 🭒🭝 🭓🭞 🭔🭟 🭕🭠 🭖🭡 🭧🭜    🭫 🭭\\n\\r');\n  term.write('   🭇🬼              🭉🬾 🭋🭀\\n\\r');\n  term.write('  🭊🭁🭌🬿 🭈🭆🭂🭍🭑🬽 🭇🭄🭏🬼 🭃🭎 🭅🭐 🭨🭪\\n\\r');\n  term.write('  🭥🭒🭝🭚 🭣🭧🭓🭞🭜🭘 🭢🭕🭠🭗 🭔🭟 🭖🭡 🭪🭨\\n\\r');\n  term.write('   🭢🭗              🭤🭙 🭦🭛\\n\\r');\n\n  term.write('\\x1b[0mCharacter cell diagonals (1FBA0-1FBAE) alignment tests:\\x1b[34m\\n\\r');\n  term.write('   \\u{1FBA3}\\u{1FBA7}\\u{1FBA2}  \\u{1FBA3}\\u{1FBA8}\\u{1FBA0} \\u{1FBAD}\\u{1FBA2} \\u{1FBA3}\\u{1FBAC} \\u{1FBAE}\\n\\r');\n  term.write('  \\u{1FBA3}\\u{1FBA0} \\u{1FBA1}\\u{1FBA2} \\u{1FBA1}\\u{1FBA9}\\u{1FBA2} \\u{1FBA1}\\u{1FBAA} \\u{1FBAB}\\u{1FBA0}\\n\\r');\n  term.write('  \\u{1FBA4}   \\u{1FBA5}\\n\\r');\n  term.write('  \\u{1FBA1}\\u{1FBA2} \\u{1FBA3}\\u{1FBA0}\\n\\r');\n  term.write('   \\u{1FBA1}\\u{1FBA6}\\u{1FBA0}\\n\\r');\n\n  term.write('\\x1b[0mCharacter cell diagonals (1FBD0-1FBDF) alignment tests:\\x1b[34m\\n\\r');\n  term.write('  \\u{1FBD6}\\u{1FBD4} \\u{1FBD0}\\u{1FBD1}\\u{1FBD2}\\u{1FBD3} \\u{1FBDA} \\u{1FBD9}\\u{1FBDB} \\u{1FBDE} \\u{1FBDD}\\u{1FBDF}\\n\\r');\n  term.write('  \\u{1FBD7}\\u{1FBD5} \\u{1FBD2}\\u{1FBD3}\\u{1FBD0}\\u{1FBD1} \\u{1FBD8}    \\u{1FBDC}\\n\\r');\n  term.write('  \\u{1FBD4}\\u{1FBD6}\\n\\r');\n  term.write('  \\u{1FBD5}\\u{1FBD7}\\n\\r');\n  term.write('');\n\n  term.write('\\x1b[0mComposite terminal graphics characters:\\x1b[35m\\n\\r');\n  term.write('\\u{1FBB2}\\u{1FBB3} \\u{1FBB9}\\u{1FBBA} \\u{1FBC1}\\u{1FBC2}\\u{1FBC3}\\n\\r');\n\n  term.write('\\x1b[0mFill tests:\\x1b[36m\\n\\r');\n  const fillChars = ['\\u{2591}', '\\u{2592}', '\\u{2593}', '\\u{1FB8C}', '\\u{1FB8D}', '\\u{1FB8E}', '\\u{1FB8F}', '\\u{1FB90}', '\\u{1FB91}', '\\u{1FB92}', '\\u{1FB94}', '\\u{1FB95}', '\\u{1FB96}', '\\u{1FB97}', '\\u{1FB98}', '\\u{1FB99}'];\n  while (fillChars.length > 0) {\n    const batch = fillChars.splice(0, 10);\n    for (const fillChar of batch) {\n      term.write(`${fillChar.codePointAt(0)!.toString(16).toUpperCase().padEnd(5, ' ')} `);\n    }\n    term.write('\\n\\r');\n    for (let i = 0; i < 3; i++) {\n      for (const fillChar of batch) {\n        term.write(fillChar.repeat(5));\n        term.write(' ');\n      }\n      term.write('\\n\\r');\n    }\n  }\n\n  term.write('\\x1b[0mTriangular fill tests:\\x1b[36m\\n\\r');\n  term.write('1FB9C 1FB9D 1FB9E 1FB9F all\\n\\r');\n  term.write('\\u{02592}\\u{02592}\\u{02592}\\u{1FB9C}  \\u{1FB9D}\\u{02592}\\u{02592}\\u{02592}  \\u{00020}\\u{00020}\\u{00020}\\u{1FB9E}  \\u{1FB9F}\\u{00020}\\u{00020}\\u{00020}  \\u{00020}\\u{1FB9E}\\u{1FB9F}\\u{00020}\\n\\r');\n  term.write('\\u{02592}\\u{02592}\\u{1FB9C}\\u{00020}  \\u{00020}\\u{1FB9D}\\u{02592}\\u{02592}  \\u{00020}\\u{00020}\\u{1FB9E}\\u{02592}  \\u{02592}\\u{1FB9F}\\u{00020}\\u{00020}  \\u{1FB9E}\\u{02592}\\u{02592}\\u{1FB9F}\\n\\r');\n  term.write('\\u{02592}\\u{1FB9C}\\u{00020}\\u{00020}  \\u{00020}\\u{00020}\\u{1FB9D}\\u{02592}  \\u{00020}\\u{1FB9E}\\u{02592}\\u{02592}  \\u{02592}\\u{02592}\\u{1FB9F}\\u{00020}  \\u{1FB9D}\\u{02592}\\u{02592}\\u{1FB9C}\\n\\r');\n  term.write('\\u{1FB9C}\\u{00020}\\u{00020}\\u{00020}  \\u{00020}\\u{00020}\\u{00020}\\u{1FB9D}  \\u{1FB9E}\\u{02592}\\u{02592}\\u{02592}  \\u{02592}\\u{02592}\\u{02592}\\u{1FB9F}  \\u{00020}\\u{1FB9D}\\u{1FB9C}\\u{00020}\\n\\r');\n\n  term.write('\\x1b[0mPowerline alignment tests:\\n\\r');\n  const powerlineLeftChars = ['\\u{E0B2}', '\\u{E0B3}', '\\u{E0B6}', '\\u{E0B7}', '\\u{E0BA}', '\\u{E0BB}', '\\u{E0BE}', '\\u{E0BF}', '\\u{E0C2}', '\\u{E0C3}', '\\u{E0C5}', '\\u{E0C7}', '\\u{E0CA}', '\\u{E0D4}'];\n  const powerlineRightChars = ['\\u{E0B0}', '\\u{E0B1}', '\\u{E0B4}', '\\u{E0B5}', '\\u{E0B8}', '\\u{E0B9}', '\\u{E0BC}', '\\u{E0BD}', '\\u{E0C0}', '\\u{E0C1}', '\\u{E0C4}', '\\u{E0C6}', '\\u{E0C8}', '\\u{E0D2}', '\\u{E0CC}', '\\u{E0CD}', '\\u{E0CE}', '\\u{E0CF}', '\\u{E0D0}', '\\u{E0D1}'];\n  for (const char of powerlineLeftChars) {\n    term.write(`\\x1b[31m${char}\\x1b[0;41m \\x1b[0m `);\n  }\n  term.write('\\n\\r');\n  for (const char of powerlineRightChars) {\n    term.write(`\\x1b[41m \\x1b[0;31m${char}\\x1b[0m `);\n  }\n  term.write('\\n\\r');\n\n  term.write('\\x1b[0mGit Branch Symbols alignment tests:\\x1b[32m\\n\\r');\n  term.write(' \\u{F5F7}\\u{F5F7} \\u{F5EE}\\u{F5EF}  \\u{F5F6}\\u{F5F7} \\u{F5D6}\\u{F5D0}\\u{F5D7} \\u{F5FC}\\u{F5FC}\\u{F5FE} \\u{F5FD}\\u{F609}\\u{F5FF}\\n\\r');\n  term.write(' \\u{F5DA}\\u{F5DD} \\u{F5F0}\\u{F5F4}\\u{F5F2} \\u{F5FA}\\u{F5FB} \\u{F5D1} \\u{F5D1} \\u{F604}\\u{F60C}\\u{F606} \\u{F605}\\u{F60D}\\u{F607}\\n\\r');\n  term.write(' \\u{F5DB}\\u{F5DE} \\u{F5F1}\\u{F5F5}\\u{F5F3} \\u{F5F8}\\u{F5F9} \\u{F5D8}\\u{F5D0}\\u{F5D9} \\u{F600}\\u{F60A}\\u{F602} \\u{F601}\\u{F60B}\\u{F603}\\n\\r');\n  term.write(' \\u{F5DC}\\u{F5DF}       \\u{F5F7}\\u{F5F7}\\u{F5F7}\\u{F5F7}\\u{F5F7}\\u{F5F7}\\u{F5F7}\\n\\r');\n  term.write('\\u{F5F1}\\u{F5E6}\\u{F5E7}\\u{F5F3} \\u{F5D3}\\u{F5D0}\\u{F5E0}\\u{F5E1}\\u{F5E2}\\u{F5E3}\\u{F5E4}\\u{F5E5}\\u{F5E8}\\u{F5E9}\\u{F5EC}\\u{F5ED}\\u{F5D2}\\n\\r');\n  term.write('\\u{F5F1}\\u{F5EA}\\u{F5EB}\\u{F5F3}   \\u{F5F9}\\u{F5F9}\\u{F5F9}   \\u{F5F9}\\u{F5F9}\\u{F5F9}\\u{F5F9}\\n\\r');\n  term.write(' \\u{F5F9}\\u{F5F9} \\n\\r');\n  term.write('\\x1b[0m');\n  term.write('\\n\\r');\n\n  term.write('\\x1b[0mProgress bar alignment tests:\\x1b[33m\\n\\r');\n  term.write('\\u{EE00}\\u{EE01}\\u{EE02} \\u{EE03}\\u{EE04}\\u{EE05}');\n\n  term.write('\\n\\r');\n  window.scrollTo(0, 0);\n}\n\nfunction customGlyphRangesHandler(term: Terminal): void {\n  // Box Drawing\n  // 2500-257F\n  // https://www.unicode.org/charts/PDF/U2500.pdf\n  writeUnicodeTable(term, 'Box Drawing', 0x2500, 0x257F, [\n    ['Light and heavy solid lines', 0x2500, 0x2503],\n    ['Light and heavy dashed lines', 0x2504, 0x250B],\n    ['Light and heavy line box components', 0x250C, 0x254B],\n    ['Light and heavy dashed lines', 0x254C, 0x254F],\n    ['Double lines', 0x2550, 0x2551],\n    ['Light and double line box components', 0x2552, 0x256C],\n    ['Character cell arcs', 0x256D, 0x2570],\n    ['Character cell diagonals', 0x2571, 0x2573],\n    ['Light and heavy half lines', 0x2574, 0x257B],\n    ['Mixed light and heavy lines', 0x257C, 0x257F],\n  ]);\n  // Box Elements\n  // 2580-259F\n  // https://www.unicode.org/charts/PDF/U2580.pdf\n  writeUnicodeTable(term, 'Box Elements', 0x2580, 0x259F, [\n    ['Block elements', 0x2580, 0x2590],\n    ['Shade characters', 0x2591, 0x2593],\n    ['Block elements', 0x2594, 0x2595],\n    ['Terminal graphic characters', 0x2596, 0x259F],\n  ]);\n  // Braille Patterns\n  // 2800-28FF\n  // https://www.unicode.org/charts/PDF/U2800.pdf\n  writeUnicodeTable(term, 'Braille patterns', 0x2800, 0x28FF, [\n    ['Braille patterns', 0x2800, 0x28FF],\n  ]);\n  // Powerline Symbols\n  // Range: E0A0–E0D4\n  // https://github.com/ryanoasis/nerd-fonts\n  writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0D4, [\n    ['Powerline symbols', 0xE0A0, 0xE0B3, [0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF]],\n    ['Powerline extra symbols', 0xE0B4, 0xE0D4, [0xE0C9, 0xE0CB, 0xE0D3]],\n  ]);\n  // Progress Indicators\n  // Range: EE00-EE0B\n  // https://github.com/tonsky/FiraCode\n  writeUnicodeTable(term, 'Progress Indicators', 0xEE00, 0xEE0B, [\n    ['Progress bars', 0xEE00, 0xEE05],\n    ['Progress spinners', 0xEE06, 0xEE0B],\n  ]);\n  // https://github.com/ryanoasis/nerd-fonts/pull/1733\n  // Git Branch Symbols\n  // F5D0-F60D\n  // https://github.com/xtermjs/xterm.js/issues/5477\n  writeUnicodeTable(term, 'Git Branch Symbols', 0xF5D0, 0xF5FB, [\n    ['Straight lines', 0xF5D0, 0xF5D5],\n    ['Curved lines', 0xF5D6, 0xF5D9],\n    ['Branching lines', 0xF5DA, 0xF5ED],\n    ['Nodes', 0xF5EE, 0xF5FB],\n    ['Extended nodes', 0xF5FC, 0xF60D],\n  ]);\n  // Symbols for Legacy Computing\n  // Range: 1FB00–1FBFF\n  // https://www.unicode.org/charts/PDF/U1FB00.pdf\n  writeUnicodeTable(term, 'Symbols for Legacy Computing', 0x1FB00, 0x1FBFF, [\n    ['Block mosaic terminal graphic characters (Sextants)', 0x1FB00, 0x1FB3B],\n    ['Smooth mosaic terminal graphic characters', 0x1FB3C, 0x1FB6F],\n    ['Block elements', 0x1FB70, 0x1FB80],\n    ['Window title bar', 0x1FB81, 0x1FB81],\n    ['Block elements', 0x1FB82, 0x1FB8B],\n    ['Rectangular shade characters', 0x1FB8C, 0x1FB94, [0x1FB93]],\n    ['Fill characters', 0x1FB95, 0x1FB97],\n    ['Diagonal fill characters', 0x1FB98, 0x1FB99],\n    ['Smooth mosaic terminal graphic characters', 0x1FB9A, 0x1FB9B],\n    ['Triangular shade characters', 0x1FB9C, 0x1FB9F],\n    ['Character cell diagonals', 0x1FBA0, 0x1FBAE],\n    ['Light solid line with stroke', 0x1FBAF, 0x1FBAF],\n    ['Terminal graphic characters', 0x1FBB0, 0x1FBB3],\n    ['Arrows', 0x1FBB4, 0x1FBB8],\n    ['Terminal graphic characters', 0x1FBB9, 0x1FBBC],\n    ['Negative terminal graphic characters', 0x1FBBD, 0x1FBBF],\n    ['Terminal graphic characters', 0x1FBC0, 0x1FBCA],\n    ['Terminal graphic characters', 0x1FBCB, 0x1FBCD],\n    ['Block elements', 0x1FBCE, 0x1FBCF],\n    ['Character cell diagonals', 0x1FBD0, 0x1FBDF],\n    ['Geometrics shapes', 0x1FBE0, 0x1FBEF],\n    ['Segmented digits', 0x1FBF0, 0x1FBF9],\n    ['Terminal graphic character', 0x1FBFA, 0x1FBFA],\n  ]);\n}\n\nfunction ansiColorsTest(term: Terminal): void {\n  term.writeln(`\\x1b[0m\\n\\n\\rStandard colors:                        Bright colors:`);\n  for (let i = 0; i < 16; i++) {\n    term.write(`\\x1b[48;5;${i}m ${i.toString().padEnd(2, ' ').padStart(3, ' ')} \\x1b[0m`);\n  }\n\n  term.writeln(`\\x1b[0m\\n\\n\\rColors 17-231 from 256 palette:`);\n  for (let i = 0; i < 6; i++) {\n    const startId = 16 + i * 36;\n    const endId = 16 + (i + 1) * 36 - 1;\n    term.write(`${startId.toString().padStart(3, ' ')}-${endId.toString().padStart(3, ' ')} `);\n    for (let j = 0; j < 36; j++) {\n      const id = 16 + i * 36 + j;\n      term.write(`\\x1b[48;5;${id}m${(id % 10).toString().padStart(2, ' ')}\\x1b[0m`);\n    }\n    term.write(`\\r\\n`);\n  }\n\n  term.writeln(`\\x1b[0m\\n\\rGreyscale from 256 palette:`);\n  term.write('232-255 ');\n  for (let i = 232; i < 256; i++) {\n    term.write(`\\x1b[48;5;${i}m ${(i % 10)} \\x1b[0m`);\n  }\n}\n\nfunction writeTestString(): string {\n  let alphabet = '';\n  for (let i = 97; i < 123; i++) {\n    alphabet += String.fromCharCode(i);\n  }\n  let numbers = '';\n  for (let i = 0; i < 10; i++) {\n    numbers += i.toString();\n  }\n  return `${alphabet} ${numbers} 汉语 한국어 👽`;\n}\nconst testString = writeTestString();\n\nfunction sgrTest(term: Terminal): void {\n  term.write('\\n\\n\\r');\n  term.writeln(`Character Attributes (SGR, Select Graphic Rendition)`);\n  const entries: { ps: number, name: string }[] = [\n    { ps: 0, name: 'Normal' },\n    { ps: 1, name: 'Bold' },\n    { ps: 2, name: 'Faint/dim' },\n    { ps: 3, name: 'Italicized' },\n    { ps: 4, name: 'Underlined' },\n    { ps: 5, name: 'Blink' },\n    { ps: 7, name: 'Inverse' },\n    { ps: 8, name: 'Invisible' },\n    { ps: 9, name: 'Crossed-out characters' },\n    { ps: 21, name: 'Doubly-underlined' },\n    { ps: 22, name: 'Normal' },\n    { ps: 23, name: 'Not italicized' },\n    { ps: 24, name: 'Not underlined' },\n    { ps: 25, name: 'Steady (not blink)' },\n    { ps: 27, name: 'Positive (not inverse)' },\n    { ps: 28, name: 'Visible (not hidden)' },\n    { ps: 29, name: 'Not crossed-out' },\n    { ps: 30, name: 'Foreground Black' },\n    { ps: 31, name: 'Foreground Red' },\n    { ps: 32, name: 'Foreground Green' },\n    { ps: 33, name: 'Foreground Yellow' },\n    { ps: 34, name: 'Foreground Blue' },\n    { ps: 35, name: 'Foreground Magenta' },\n    { ps: 36, name: 'Foreground Cyan' },\n    { ps: 37, name: 'Foreground White' },\n    { ps: 39, name: 'Foreground default' },\n    { ps: 40, name: 'Background Black' },\n    { ps: 41, name: 'Background Red' },\n    { ps: 42, name: 'Background Green' },\n    { ps: 43, name: 'Background Yellow' },\n    { ps: 44, name: 'Background Blue' },\n    { ps: 45, name: 'Background Magenta' },\n    { ps: 46, name: 'Background Cyan' },\n    { ps: 47, name: 'Background White' },\n    { ps: 49, name: 'Background default' },\n    { ps: 53, name: 'Overlined' },\n    { ps: 55, name: 'Not overlined' },\n    { ps: 221, name: 'Not bold (kitty)' },\n    { ps: 222, name: 'Not faint (kitty)' }\n  ];\n  const maxNameLength = entries.reduce<number>((p, c) => Math.max(c.name.length, p), 0);\n  for (const e of entries) {\n    term.writeln(`\\x1b[0m\\x1b[${e.ps}m ${e.ps.toString().padEnd(2, ' ')} ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\\x1b[0m`);\n  }\n  const entriesByPs: Map<number, string> = new Map();\n  for (const e of entries) {\n    entriesByPs.set(e.ps, e.name);\n  }\n  const comboEntries: { ps: number[] }[] = [\n    { ps: [1, 2, 3, 4, 5, 6, 7, 9] },\n    { ps: [2, 41] },\n    { ps: [4, 53] },\n    { ps: [1, 2, 221] },\n    { ps: [1, 2, 222] }\n  ];\n  term.write('\\n\\n\\r');\n  term.writeln(`Combinations`);\n  for (const e of comboEntries) {\n    const name = e.ps.map(e => entriesByPs.get(e)).join(', ');\n    term.writeln(`\\x1b[0m\\x1b[${e.ps.join(';')}m ${name}\\n\\r${testString}\\x1b[0m`);\n  }\n}\n\nfunction addAnsiHyperlink(term: Terminal): void {\n  term.write('\\n\\n\\r');\n  term.writeln(`Regular link with no id:`);\n  term.writeln('\\x1b]8;;https://github.com\\x07GitHub\\x1b]8;;\\x07');\n  term.writeln('\\x1b]8;;https://xtermjs.org\\x07https://xtermjs.org\\x1b]8;;\\x07\\x1b[C<- null cell');\n  term.writeln(`\\nAdjacent links:`);\n  term.writeln('\\x1b]8;;https://github.com\\x07GitHub\\x1b]8;;https://xtermjs.org\\x07\\x1b[32mxterm.js\\x1b[0m\\x1b]8;;\\x07');\n  term.writeln(`\\nShared ID link (underline should be shared):`);\n  term.writeln('╔════╗');\n  term.writeln('║\\x1b]8;id=testid;https://github.com\\x07GitH\\x1b]8;;\\x07║');\n  term.writeln('║\\x1b]8;id=testid;https://github.com\\x07ub\\x1b]8;;\\x07  ║');\n  term.writeln('╚════╝');\n  term.writeln(`\\nWrapped link with no ID (not necessarily meant to share underline):`);\n  term.writeln('╔════╗');\n  term.writeln('║    ║');\n  term.writeln('║    ║');\n  term.writeln('╚════╝');\n  term.write('\\x1b[3A\\x1b[1C\\x1b]8;;https://xtermjs.org\\x07xter\\x1b[B\\x1b[4Dm.js\\x1b]8;;\\x07\\x1b[2B\\x1b[5D');\n}\n\nfunction addGraphemeClusters(term: Terminal): void {\n  term.write('\\n\\n\\r');\n  term.writeln('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 [Simple emoji v6: 10 cells, v15: 20 cells]');\n  term.writeln('\\u{1F476}\\u{1F3FF}\\u{1F476} [baby with emoji modifier fitzpatrick type-6; baby]');\n  term.writeln('\\u{1F469}\\u200d\\u{1f469}\\u200d\\u{1f466} [woman+zwj+woman+zwj+boy]');\n  term.writeln('\\u{1F64B}\\u{1F64B}\\u{200D}\\u{2642}\\u{FE0F} [person/man raising hand]');\n  term.writeln('\\u{1F3CB}\\u{FE0F}=\\u{1F3CB}\\u{1F3FE}\\u{200D}\\u{2640}\\u{FE0F} [person lifting weights emoji; woman lighting weights, medium dark]');\n  term.writeln('\\u{1F469}\\u{1F469}\\u{200D}\\u{1F393}\\u{1F468}\\u{1F3FF}\\u{200D}\\u{1F393} [woman; woman student; man student dark]');\n  term.writeln('\\u{1f1f3}\\u{1f1f4}_ [REGIONAL INDICATOR SYMBOL LETTER N and RI O]');\n  term.writeln('\\u{1f1f3}_\\u{1f1f4} {RI N; underscore; RI O]');\n  term.writeln('\\u0061\\u0301 [letter a with acute accent]');\n  term.writeln('\\u1100\\u1161\\u11A8=\\u1100\\u1161= [Korean Jamo]');\n  term.writeln('\\uAC00=\\uD685= [Hangul syllables (pre-composed)]');\n  term.writeln('(\\u26b0\\ufe0e) [coffin with text_presentation]');\n  term.writeln('(\\u26b0\\ufe0f) [coffin with Emoji_presentation]');\n  term.writeln('<E\\u0301\\ufe0fg\\ufe0fa\\ufe0fl\\ufe0fi\\ufe0f\\ufe0ft\\ufe0fe\\u0301\\ufe0f> [Égalité (using separate acute) emoij_presentation]');\n}\n\nfunction coloredErase(term: Terminal): void {\n  const sp5 = '     ';\n  const data = `\nTest BG-colored Erase (BCE):\n  The color block in the following lines should look identical.\n  For newly created rows at the bottom the last color should be applied\n  for all cells to the right.\n\n def   41   42   43   44   45   46   47\\x1b[47m\n\\x1b[m${sp5}\\x1b[41m${sp5}\\x1b[42m${sp5}\\x1b[43m${sp5}\\x1b[44m${sp5}\\x1b[45m${sp5}\\x1b[46m${sp5}\\x1b[47m${sp5}\n\\x1b[m\\x1b[5X\\x1b[41m\\x1b[5C\\x1b[5X\\x1b[42m\\x1b[5C\\x1b[5X\\x1b[43m\\x1b[5C\\x1b[5X\\x1b[44m\\x1b[5C\\x1b[5X\\x1b[45m\\x1b[5C\\x1b[5X\\x1b[46m\\x1b[5C\\x1b[5X\\x1b[47m\\x1b[5C\\x1b[5X\\x1b[m\n`;\n  term.write(data.split('\\n').join('\\r\\n'));\n}\n\nfunction loadTest(term: Terminal, addons: AddonCollection): void {\n  const rendererName = addons.webgl.instance ? 'webgl' : 'dom';\n  const testData = [];\n  let byteCount = 0;\n  for (let i = 0; i < 50; i++) {\n    const count = 1 + Math.floor(Math.random() * 79);\n    byteCount += count + 2;\n    const data = new Uint8Array(count + 2);\n    data[0] = 0x0A; // \\n\n    for (let i = 1; i < count + 1; i++) {\n      data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));\n    }\n    // End each line with \\r so the cursor remains constant, this is what ls/tree do and improves\n    // performance significantly due to the cursor DOM element not needing to change\n    data[data.length - 1] = 0x0D; // \\r\n    testData.push(data);\n  }\n  const start = performance.now();\n  for (let i = 0; i < 1024; i++) {\n    for (const d of testData) {\n      term.write(d);\n    }\n  }\n  // Wait for all data to be parsed before evaluating time\n  term.write('', () => {\n    const time = Math.round(performance.now() - start);\n    const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);\n    term.write(`\\n\\r\\nWrote ${byteCount}KiB in ${time}ms (${mbs}MiB/s) using the (${rendererName} renderer)`);\n    // Send ^C to get a new prompt\n    (term as any)._core._onData.fire('\\x03');\n  });\n}\n\nasync function loadTestLongLines(term: Terminal, addons: AddonCollection): Promise<void> {\n  const rendererName = addons.webgl.instance ? 'webgl' : 'dom';\n  const testData = [];\n  let byteCount = 0;\n  for (let i = 0; i < 50; i++) {\n    const count = 1 + Math.floor(Math.random() * 500);\n    byteCount += count + 2;\n    const data = new Uint8Array(count + 2);\n    data[0] = 0x0A; // \\n\n    for (let i = 1; i < count + 1; i++) {\n      data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));\n    }\n    // End each line with \\r so the cursor remains constant, this is what ls/tree do and improves\n    // performance significantly due to the cursor DOM element not needing to change\n    data[data.length - 1] = 0x0D; // \\r\n    testData.push(data);\n  }\n  const start = performance.now();\n  for (let i = 0; i < 1024; i++) {\n    for (const d of testData) {\n      try {\n        term.write(d);\n      } catch {\n        // Flush events when cap is hit and try again(workaround for not having flow control in\n        // demo)\n        await new Promise<void>(r => term.write('', () => r()));\n        term.write(d);\n      }\n    }\n  }\n  // Wait for all data to be parsed before evaluating time\n  term.write('', () => {\n    const time = Math.round(performance.now() - start);\n    const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);\n    term.write(`\\n\\r\\nWrote ${byteCount}KiB in ${time}ms (${mbs}MiB/s) using the (${rendererName} renderer)`);\n    // Send ^C to get a new prompt\n    (term as any)._core._onData.fire('\\x03');\n  });\n}\n\nfunction addDecoration(term: Terminal, dim: number = 1): void {\n  term.options.scrollbar = { ...(term.options.scrollbar ?? {}), width: 14, overviewRuler: term.options.scrollbar?.overviewRuler ?? {} };\n  const marker = term.registerMarker(1);\n  const decoration = term.registerDecoration({\n    marker,\n    height: dim,\n    width: dim,\n    backgroundColor: '#00FF00',\n    foregroundColor: '#00FE00',\n    overviewRulerOptions: { color: '#ef292980', position: 'left' }\n  });\n  decoration?.onRender((e: HTMLElement) => {\n    e.style.right = '100%';\n    e.style.backgroundColor = '#ef292980';\n  });\n}\n\nfunction addOverviewRuler(term: Terminal): void {\n  term.options.scrollbar = { ...(term.options.scrollbar ?? {}), width: 14, overviewRuler: term.options.scrollbar?.overviewRuler ?? {} };\n  term.registerDecoration({ marker: term.registerMarker(1), overviewRulerOptions: { color: '#ef2929' } });\n  term.registerDecoration({ marker: term.registerMarker(3), overviewRulerOptions: { color: '#8ae234' } });\n  term.registerDecoration({ marker: term.registerMarker(5), overviewRulerOptions: { color: '#729fcf' } });\n  term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' } });\n  term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' } });\n  term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' } });\n  term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' } });\n  term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' } });\n}\n\nlet decorationStressTestDecorations: IDisposable[] | undefined;\nfunction decorationStressTest(term: Terminal): void {\n  if (decorationStressTestDecorations) {\n    for (const d of decorationStressTestDecorations) {\n      d.dispose();\n    }\n    decorationStressTestDecorations = undefined;\n  } else {\n    const buffer = term.buffer.active;\n    const cursorY = buffer.baseY + buffer.cursorY;\n    decorationStressTestDecorations = [];\n    for (const x of [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]) {\n      for (let y = 0; y < term.buffer.active.length; y++) {\n        const cursorOffsetY = y - cursorY;\n        const decoration = term.registerDecoration({\n          marker: term.registerMarker(cursorOffsetY),\n          x,\n          width: 4,\n          backgroundColor: '#FF0000',\n          overviewRulerOptions: { color: '#FF0000' }\n        });\n        if (decoration) {\n          decorationStressTestDecorations.push(decoration);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/vtWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class VtWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'vt';\n  public readonly label = 'VT';\n\n  private _container!: HTMLElement;\n\n  public build(container: HTMLElement): void {\n    this._container = container;\n\n    const vtContainer = document.createElement('div');\n    vtContainer.id = 'vt-container';\n    container.appendChild(vtContainer);\n\n    const vtFragment = document.createDocumentFragment();\n    const buttonSpecs: { [key: string]: { label: string, description: string, paramCount?: number } } = {\n      'A': { label: 'CUU ↑', description: 'Cursor Up Ps Times' },\n      'B': { label: 'CUD ↓', description: 'Cursor Down Ps Times' },\n      'C': { label: 'CUF →', description: 'Cursor Forward Ps Times' },\n      'D': { label: 'CUB ←', description: 'Cursor Backward Ps Times' },\n      'E': { label: 'CNL', description: 'Cursor Next Line Ps Times' },\n      'F': { label: 'CPL', description: 'Cursor Preceding Line Ps Times' },\n      'G': { label: 'CHA', description: 'Cursor Character Absolute' },\n      'H': { label: 'CUP', description: 'Cursor Position [row;column]', paramCount: 2 },\n      'I': { label: 'CHT', description: 'Cursor Forward Tabulation Ps tab stops' },\n      'J': { label: 'ED', description: 'Erase in Display' },\n      '?|J': { label: 'DECSED', description: 'Erase in Display' },\n      'K': { label: 'EL', description: 'Erase in Line' },\n      '?|K': { label: 'DECSEL', description: 'Erase in Line' },\n      'L': { label: 'IL', description: 'Insert Ps Line(s)' },\n      'M': { label: 'DL', description: 'Delete Ps Line(s)' },\n      'P': { label: 'DCH', description: 'Delete Ps Character(s)' },\n      ' q': { label: 'DECSCUSR', description: 'Set Cursor Style' },\n      '?2026h': { label: 'BSU', description: 'Begin synchronized update', paramCount: 0 },\n      '?2026l': { label: 'ESU', description: 'End synchronized update', paramCount: 0 }\n    };\n    for (const s of Object.keys(buttonSpecs)) {\n      const spec = buttonSpecs[s];\n      vtFragment.appendChild(this._createButton(spec.label, spec.description, s, spec.paramCount));\n    }\n\n    vtContainer.appendChild(vtFragment);\n  }\n\n  private _createButton(name: string, description: string, writeCsi: string, paramCount: number = 1): HTMLElement {\n    const inputs: HTMLInputElement[] = [];\n    for (let i = 0; i < paramCount; i++) {\n      const input = document.createElement('input');\n      input.type = 'number';\n      input.title = `Input #${i + 1}`;\n      inputs.push(input);\n    }\n\n    const element = document.createElement('button');\n    element.textContent = name;\n    const writeCsiSplit = writeCsi.split('|');\n    const prefix = writeCsiSplit.length === 2 ? writeCsiSplit[0] : '';\n    const suffix = writeCsiSplit[writeCsiSplit.length - 1];\n    element.addEventListener('click', () => this._terminal.write(this._csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)));\n\n    const desc = document.createElement('span');\n    desc.textContent = description;\n\n    const container = document.createElement('div');\n    container.classList.add('vt-button');\n    container.append(element, ...inputs, desc);\n    return container;\n  }\n\n  private _csi(e: string): string {\n    return `\\x1b[${e}`;\n  }\n}\n"
  },
  {
    "path": "demo/client/components/window/webglWindow.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BaseWindow } from './baseWindow';\nimport type { IControlWindow } from '../controlBar';\n\nexport class WebglWindow extends BaseWindow implements IControlWindow {\n  public readonly id = 'addon-webgl';\n  public readonly label = 'webgl';\n\n  private _textureAtlasContainer!: HTMLElement;\n\n  public build(container: HTMLElement): void {\n    const zoomCheckbox = document.createElement('input');\n    zoomCheckbox.type = 'checkbox';\n    zoomCheckbox.id = 'texture-atlas-zoom';\n    container.appendChild(zoomCheckbox);\n\n    const zoomLabel = document.createElement('label');\n    zoomLabel.htmlFor = 'texture-atlas-zoom';\n    zoomLabel.textContent = 'Zoom texture atlas';\n    container.appendChild(zoomLabel);\n\n    this._textureAtlasContainer = document.createElement('div');\n    this._textureAtlasContainer.id = 'texture-atlas';\n    container.appendChild(this._textureAtlasContainer);\n  }\n\n  public setTextureAtlas(canvas: HTMLCanvasElement): void {\n    this._styleAtlasPage(canvas);\n    this._textureAtlasContainer.replaceChildren(canvas);\n  }\n\n  public appendTextureAtlas(canvas: HTMLCanvasElement): void {\n    this._styleAtlasPage(canvas);\n    this._textureAtlasContainer.appendChild(canvas);\n  }\n\n  public removeTextureAtlas(canvas: HTMLCanvasElement): void {\n    canvas.remove();\n  }\n\n  private _styleAtlasPage(canvas: HTMLCanvasElement): void {\n    // eslint-disable-next-line no-restricted-syntax\n    const dpr = window.devicePixelRatio;\n    canvas.style.width = `${canvas.width / dpr}px`;\n    canvas.style.height = `${canvas.height / dpr}px`;\n  }\n}\n"
  },
  {
    "path": "demo/client/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"outDir\": \"../out-demo/client\",\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"@xterm/addon-attach\": [\"../../addons/addon-attach\"],\n      \"@xterm/addon-clipboard\": [\"../../addons/addon-clipboard\"],\n      \"@xterm/addon-fit\": [\"../../addons/addon-fit\"],\n      \"@xterm/addon-image\": [\"../../addons/addon-image\"],\n      \"@xterm/addon-progress\": [\"../../addons/addon-progress\"],\n      \"@xterm/addon-search\": [\"../../addons/addon-search\"],\n      \"@xterm/addon-serialize\": [\"../../addons/addon-serialize\"],\n      \"@xterm/addon-web-fonts\": [\"../../addons/addon-web-fonts\"],\n      \"@xterm/addon-web-links\": [\"../../addons/addon-web-links\"],\n      \"@xterm/addon-webgl\": [\"../../addons/addon-webgl\"],\n      \"@xterm/addon-unicode11\": [\"../../addons/addon-unicode11\"],\n      \"@xterm/addon-unicode-graphemes\": [\"../../addons/addon-unicode-graphemes\"],\n      \"@xterm/addon-ligatures\": [\"../../addons/addon-ligatures\"],\n      \"*\": [\"./*\"]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "demo/client/types.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This file is the entry point for browserify.\n */\n\nimport type { ImageAddon } from '@xterm/addon-image';\nimport type { AttachAddon } from '@xterm/addon-attach';\nimport type { ClipboardAddon } from '@xterm/addon-clipboard';\nimport type { FitAddon } from '@xterm/addon-fit';\nimport type { LigaturesAddon } from '@xterm/addon-ligatures';\nimport type { ProgressAddon } from '@xterm/addon-progress';\nimport type { SearchAddon } from '@xterm/addon-search';\nimport type { SerializeAddon } from '@xterm/addon-serialize';\nimport type { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';\nimport type { Unicode11Addon } from '@xterm/addon-unicode11';\nimport type { WebFontsAddon } from '@xterm/addon-web-fonts';\nimport type { WebLinksAddon } from '@xterm/addon-web-links';\nimport type { WebglAddon } from '@xterm/addon-webgl';\n\nexport type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'ligatures' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webFonts' | 'webLinks' | 'webgl';\n\nexport interface IDemoAddon<T extends AddonType> {\n  name: T;\n  canChange: boolean;\n  ctor: (\n    T extends 'attach' ? typeof AttachAddon :\n      T extends 'clipboard' ? typeof ClipboardAddon :\n        T extends 'fit' ? typeof FitAddon :\n          T extends 'image' ? typeof ImageAddon :\n            T extends 'ligatures' ? typeof LigaturesAddon :\n              T extends 'progress' ? typeof ProgressAddon :\n                T extends 'search' ? typeof SearchAddon :\n                  T extends 'serialize' ? typeof SerializeAddon :\n                    T extends 'webFonts' ? typeof WebFontsAddon :\n                      T extends 'webLinks' ? typeof WebLinksAddon :\n                        T extends 'unicode11' ? typeof Unicode11Addon :\n                          T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :\n                            T extends 'webgl' ? typeof WebglAddon :\n                              never\n  );\n  instance?: (\n    T extends 'attach' ? AttachAddon :\n      T extends 'clipboard' ? ClipboardAddon :\n        T extends 'fit' ? FitAddon :\n          T extends 'image' ? ImageAddon :\n            T extends 'ligatures' ? LigaturesAddon :\n              T extends 'progress' ? ProgressAddon :\n                T extends 'search' ? SearchAddon :\n                  T extends 'serialize' ? SerializeAddon :\n                    T extends 'webFonts' ? WebFontsAddon :\n                      T extends 'webLinks' ? WebLinksAddon :\n                        T extends 'unicode11' ? Unicode11Addon :\n                          T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :\n                            T extends 'webgl' ? WebglAddon :\n                              never\n  );\n}\n\nexport type AddonCollection = { [T in AddonType]: IDemoAddon<T> };\n"
  },
  {
    "path": "demo/client/unicodeTable.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from '@xterm/xterm';\n\nexport type UnicodeRangeDefinition = [\n  label: string,\n  start: number,\n  end: number,\n  reserved?: number[],\n];\n\n/**\n * Write a unicode table to the terminal from start to end code points.\n *\n * Beware: Vibe coding ahead\n */\nexport function writeUnicodeTable(term: Terminal, name: string, start: number, end: number, definitions?: UnicodeRangeDefinition[]): void {\n  function bold(text: string): string {\n    return '\\x1b[1m' + text + '\\x1b[22m';\n  }\n  function faint(text: string): string {\n    return '\\x1b[2m' + text + '\\x1b[22m';\n  }\n  // Rotating colors: Red, Green, Yellow, Blue, Magenta, Cyan\n  const colors = [31, 32, 33, 34, 35, 36];\n  function color(text: string, colorIndex: number): string {\n    const c = colors[colorIndex % colors.length];\n    return '\\x1b[' + c + 'm' + text + '\\x1b[39m';\n  }\n\n  term.write('\\n\\r');\n  term.write('\\n\\r');\n  term.write(`${bold(name)} (${start.toString(16).toUpperCase()}-${end.toString(16).toUpperCase()})\\n\\r`);\n  term.write('\\n\\r');\n  term.write(bold('         0 1 2 3 4 5 6 7 8 9 A B C D E F') + '\\n\\r');\n\n  const startRow = Math.floor(start / 16);\n\n  // Build a map of codepoint -> label and colorIndex for start positions\n  const labelStartMap = new Map<number, { label: string, colorIndex: number }>();\n  // Build a map of codepoint -> colorIndex for all codepoints in each range\n  const codePointColorMap = new Map<number, number>();\n  // Build a set of reserved codepoints\n  const reservedSet = new Set<number>();\n  let lastDefinitionEnd = start; // Track the last definition's end to stop printing there\n  if (definitions) {\n    for (let i = 0; i < definitions.length; i++) {\n      const [label, labelStart, labelEnd, reserved] = definitions[i];\n      labelStartMap.set(labelStart, { label, colorIndex: i });\n      // Map all codepoints in the range to this color\n      for (let cp = labelStart; cp <= labelEnd; cp++) {\n        codePointColorMap.set(cp, i);\n      }\n      // Track reserved codepoints\n      if (reserved) {\n        for (const cp of reserved) {\n          reservedSet.add(cp);\n        }\n      }\n      if (labelEnd > lastDefinitionEnd) {\n        lastDefinitionEnd = labelEnd;\n      }\n    }\n  }\n\n  const effectiveEnd = definitions ? lastDefinitionEnd : end;\n  const endRow = Math.floor(effectiveEnd / 16);\n\n  for (let row = startRow; row <= endRow; row++) {\n    // Collect labels for this row with their column positions and color\n    const rowLabels: { col: number, label: string, colorIndex: number }[] = [];\n    // Collect reserved codepoints for this row\n    const rowReserved: { col: number, colorIndex: number }[] = [];\n    for (let col = 0; col < 16; col++) {\n      const codePoint = row * 16 + col;\n      const labelInfo = labelStartMap.get(codePoint);\n      if (labelInfo) {\n        rowLabels.push({ col, label: labelInfo.label, colorIndex: labelInfo.colorIndex });\n      }\n      if (reservedSet.has(codePoint)) {\n        const charColorIndex = codePointColorMap.get(codePoint);\n        rowReserved.push({ col, colorIndex: charColorIndex ?? -1 });\n      }\n    }\n\n    // If labels exist and don't start at column 0, first output chars before first label\n    if (rowLabels.length > 0 && rowLabels[0].col > 0) {\n      const rowHex = row.toString(16).toUpperCase();\n      const rowPrefix = `U+${rowHex}x`.padEnd(8, ' ');\n      term.write(bold(rowPrefix));\n      for (let col = 0; col < rowLabels[0].col; col++) {\n        const codePoint = row * 16 + col;\n        term.write(' ');\n        if (codePoint >= start && codePoint <= end) {\n          const charColorIndex = codePointColorMap.get(codePoint);\n          const isReserved = reservedSet.has(codePoint);\n          let char = String.fromCodePoint(codePoint);\n          if (charColorIndex !== undefined) {\n            char = color(char, charColorIndex);\n          }\n          if (isReserved) {\n            char = faint(char);\n          }\n          term.write(char);\n        } else {\n          term.write(' ');\n        }\n      }\n      term.write('\\n\\r');\n\n      // Render reserved labels that appear before the first label (below the first part of row)\n      const earlyReserved = rowReserved.filter(r => r.col < rowLabels[0].col);\n      if (earlyReserved.length > 0) {\n        for (let i = earlyReserved.length - 1; i >= 0; i--) {\n          const prefix = ' '.repeat(8);\n          let line = '';\n          let visualLen = 0;\n          for (let col = 0; col < rowLabels[0].col; col++) {\n            const colPos = col * 2 + 1; // +1 to align with character position\n            const reservedAtCol = earlyReserved.findIndex(r => r.col === col);\n            if (reservedAtCol === i) {\n              const padding = ' '.repeat(colPos - visualLen);\n              const reservedItem = earlyReserved[i];\n              if (reservedItem.colorIndex >= 0) {\n                line += padding + color('└<reserved>', reservedItem.colorIndex);\n              } else {\n                line += padding + '└<reserved>';\n              }\n              break;\n            } else if (reservedAtCol !== -1 && reservedAtCol < i) {\n              const padding = ' '.repeat(colPos - visualLen);\n              const prevReserved = earlyReserved[reservedAtCol];\n              if (prevReserved.colorIndex >= 0) {\n                line += padding + color('│', prevReserved.colorIndex);\n              } else {\n                line += padding + '│';\n              }\n              visualLen = colPos + 1;\n            }\n          }\n          term.write(faint(prefix + line) + '\\n\\r');\n        }\n      }\n    }\n\n    // If labels exist, render them above the row\n    if (rowLabels.length > 0) {\n      // Render label lines from top to bottom (first label on top, last label closest to row)\n      for (let i = 0; i < rowLabels.length; i++) {\n        const prefix = ' '.repeat(8); // Same width as \"U+1FB0x  \"\n        let line = '';\n        let visualLen = 0; // Track visual length separately from string length (escape codes don't count)\n        for (let col = 0; col < 16; col++) {\n          const colPos = col * 2; // Each char takes 2 positions (char + space)\n          const labelAtCol = rowLabels.findIndex(l => l.col === col);\n          if (labelAtCol === i) {\n            // This is where we show the label\n            const padding = ' '.repeat(colPos - visualLen);\n            line += padding + color('┌' + rowLabels[i].label, rowLabels[i].colorIndex);\n            break;\n          } else if (labelAtCol !== -1 && labelAtCol < i) {\n            // Show vertical line for labels that were already rendered above\n            const padding = ' '.repeat(colPos - visualLen);\n            line += padding + color('│', rowLabels[labelAtCol].colorIndex);\n            visualLen = colPos + 1;\n          }\n        }\n        term.write(faint(prefix + line) + '\\n\\r');\n      }\n    }\n\n    // Row prefix (e.g., \"U+1FB0x  \")\n    const rowHex = row.toString(16).toUpperCase();\n    const rowPrefix = `U+${rowHex}x`.padEnd(8, ' ');\n    const isSecondPrint = rowLabels.length > 0 && rowLabels[0].col > 0;\n    term.write(isSecondPrint ? ' '.repeat(rowPrefix.length) : bold(rowPrefix));\n\n    // Determine starting column (skip chars already output if we had labels not at col 0)\n    const startCol = isSecondPrint ? rowLabels[0].col : 0;\n\n    // Determine ending column (stop at effectiveEnd on the last row)\n    const endCol = (row === endRow) ? (effectiveEnd % 16) + 1 : 16;\n\n    // Pad for skipped columns\n    for (let col = 0; col < startCol; col++) {\n      term.write('  ');\n    }\n\n    // Characters in this row\n    for (let col = startCol; col < endCol; col++) {\n      const codePoint = row * 16 + col;\n\n      // Check if a label starts here\n      const labelInfo = labelStartMap.get(codePoint);\n      if (labelInfo) {\n        term.write(faint(color('└', labelInfo.colorIndex)));\n      } else {\n        term.write(' ');\n      }\n\n      if (codePoint >= start && codePoint <= effectiveEnd) {\n        // Color the character if it's part of a definition range\n        const charColorIndex = codePointColorMap.get(codePoint);\n        const isReserved = reservedSet.has(codePoint);\n        let char = String.fromCodePoint(codePoint);\n        if (charColorIndex !== undefined) {\n          char = color(char, charColorIndex);\n        }\n        if (isReserved) {\n          char = faint(char);\n        }\n        term.write(char);\n      } else {\n        term.write(' ');\n      }\n    }\n\n    term.write('\\n\\r');\n\n    // Render reserved labels that appear after the first label (below the row)\n    // Show one label per non-contiguous reserved range\n    const lateReserved = rowLabels.length > 0\n      ? rowReserved.filter(r => r.col >= rowLabels[0].col)\n      : rowReserved;\n    if (lateReserved.length > 0) {\n      // Group contiguous reserved ranges\n      const reservedGroups: { startCol: number, colorIndex: number }[] = [];\n      for (let i = 0; i < lateReserved.length; i++) {\n        const curr = lateReserved[i];\n        const prev = lateReserved[i - 1];\n        // Start a new group if not contiguous (gap of more than 1 column)\n        if (i === 0 || curr.col > prev.col + 1) {\n          reservedGroups.push({ startCol: curr.col, colorIndex: curr.colorIndex });\n        }\n      }\n\n      // Render from bottom to top (last group at bottom with └, earlier groups with │)\n      for (let i = reservedGroups.length - 1; i >= 0; i--) {\n        const prefix = ' '.repeat(8);\n        let line = '';\n        let visualLen = 0;\n        for (let g = 0; g <= i; g++) {\n          const group = reservedGroups[g];\n          const colPos = group.startCol * 2 + 1;\n          const padding = ' '.repeat(colPos - visualLen);\n          if (g === i) {\n            // This is the label for this line\n            if (group.colorIndex >= 0) {\n              line += padding + color('└<reserved>', group.colorIndex);\n            } else {\n              line += padding + '└<reserved>';\n            }\n          } else {\n            // Vertical connector for groups below\n            if (group.colorIndex >= 0) {\n              line += padding + color('│', group.colorIndex);\n            } else {\n              line += padding + '│';\n            }\n            visualLen = colPos + 1;\n          }\n        }\n        term.write(faint(prefix + line) + '\\n\\r');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/fonts/font-licenses.txt",
    "content": "\nBPdots is licensed under the Creative Commons Attribution-NoDerivs License (CC BY-ND).\n\n\nkongtext license text:\n\nThanks for downloading one of codeman38's retro video game fonts,\nas seen on Memepool, BoingBoing, and all around the blogosphere.\n\nSo, you're wondering what the license is for these fonts? Pretty simple;\nit's based upon that used for Bitstream's Vera font set <http://www.gnome.org/fonts/>.\n\nBasically, here are the key points summarized, in as little legalese as possible;\nI hate reading license agreements as much as you probably do:\n\nWith one specific exception, you have full permission to bundle these fonts in\nyour own free or commercial projects-- and by projects, I'm referring to not\njust software but also electronic documents and print publications.\n\nSo what's the exception? Simple: you can't re-sell these fonts\nin a commercial font collection. I've seen too many font CDs for sale in stores\nthat are just a repackaging of thousands of freeware fonts found on the internet,\nand in my mind, that's quite a bit like highway robbery. Note that this *only*\napplies to products that are font collections in and of themselves;\nyou may freely bundle these fonts with an operating system, application program,\nor the like.\n\nFeel free to modify these fonts and even to release the modified versions,\nas long as you change the original font names (to ensure consistency among\npeople with the font installed) and as long as you give credit somewhere\nin the font file to codeman38 or zone38.net. I may even incorporate these changes\ninto a later version of my fonts if you wish to send me the modifed fonts via e-mail.\n\nAlso, feel free to mirror these fonts on your own site, as long as you make it\nreasonably clear that these fonts are not your own work. I'm not asking for much;\nlinking to zone38.net or even just mentioning the nickname codeman38 should be enough.\n\nWell, that pretty much sums it up... so without further ado,\ninstall and enjoy these fonts from the golden age of video games.\n\n[ codeman38 | cody@zone38.net | http://www.zone38.net/ ]\n"
  },
  {
    "path": "demo/index.css",
    "content": "body {\n    font-family: helvetica, sans-serif, arial;\n    font-size: 1em;\n    color: #111;\n    margin: 0;\n    padding: 0;\n    height: 100vh;\n    display: grid;\n    grid-template-rows: 24px 1fr;\n    overflow: hidden;\n}\n\n#banner {\n    background: #161716;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 0 15px;\n}\n.banner-title {\n    color: #5DA5D5;\n    font-weight: bold;\n    font-size: 14px;\n}\n.banner-tabs {\n    display: flex;\n    gap: 2px;\n}\n.banner-tabs button {\n    background: transparent;\n    border: none;\n    color: #ccc;\n    padding: 4px 10px;\n    cursor: pointer;\n    font-size: 12px;\n    outline-offset: -1px;\n}\n.banner-tabs button:hover {\n    background: rgba(255,255,255,0.1);\n}\n.banner-tabs button.active {\n    background: rgba(255,255,255,0.2);\n    color: #fff;\n}\n\np {\n    font-size: 0.9em;\n    font-style: italic\n    \n}\ndl:first-child,\np:first-child {\n    margin-top: 0;\n}\n\n#option-container {\n    display: flex;\n    justify-content: center;\n}\n\n.option-group {\n    display: inline-block;\n    padding-left: 20px;\n    vertical-align: top;\n}\n\npre {\n    display: block;\n    padding: 9.5px;\n    font-size: 13px;\n    color: #c7254e;\n    background-color: #f9f2f4;\n    word-break: break-all;\n    word-wrap: break-word;\n    white-space: pre-wrap;\n}\n\n\n#container {\n    display: flex;\n    position: relative;\n    min-height: 0;\n}\n.grid {\n    flex: 1;\n    width: 100%;\n    min-width: 100px;\n}\ndiv:first-of-type.grid {\n    flex: 2;\n    height: 100%;\n}\n\n/* Sidebar */\n#sidebar {\n    position: fixed;\n    top: 24px;\n    right: 15px; /* Room for scrollbar */\n    width: 400px;\n    max-width: calc(100vw - 30px); /* Don't exceed screen width minus scrollbar */\n    height: 500px;\n    background: #161716;\n    color: #fff;\n    border: 1px solid #333;\n    border-top: none;\n    box-shadow: 0 2px 10px rgba(0,0,0,0.4);\n    z-index: 1000;\n    overflow: auto;\n    flex: none;\n    opacity: 0.5;\n    transition: opacity 0.1s;\n}\n#sidebar:hover {\n    opacity: 1;\n}\n#sidebar.sidebar-hidden {\n    display: none;\n}\n\n/* Resize handles */\n#sidebar-resize-handle-horizontal {\n    position: absolute;\n    left: 0;\n    top: 0;\n    bottom: 0;\n    width: 6px;\n    cursor: ew-resize;\n    background: transparent;\n}\n#sidebar-resize-handle-horizontal:hover {\n    background: rgba(0, 120, 212, 0.3);\n}\n#sidebar-resize-handle-vertical {\n    position: absolute;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    height: 6px;\n    cursor: ns-resize;\n    background: transparent;\n}\n#sidebar-resize-handle-vertical:hover {\n    background: rgba(0, 120, 212, 0.3);\n}\n#sidebar-resize-handle-corner {\n    position: absolute;\n    left: 0;\n    bottom: 0;\n    width: 12px;\n    height: 12px;\n    cursor: nesw-resize;\n    background: transparent;\n}\n#sidebar-resize-handle-corner:hover {\n    background: rgba(0, 120, 212, 0.5);\n}\n\n/* Style the tab content */\n.tabContent {\n    display: none;\n    padding: 6px 12px;\n    border-top: none;\n}\n\n#texture-atlas-zoom:checked ~ #texture-atlas canvas {\n    /* Zoom atlas to the width of the container*/\n    width: 100% !important;\n    height: auto !important;\n}\n#texture-atlas {\n    width: 100%;\n}\n#texture-atlas canvas {\n    image-rendering: pixelated;\n    border: 1px solid #ccc;\n}\n\n.vt-button * {\n  margin-right: 1em;\n}\ninput#opt-cols_rows {\n    width: 6em;\n}\n"
  },
  {
    "path": "demo/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>xterm.js demo</title>\n    <!--\n      WARNING: This demo is a barebones implementation designed for development and evaluation\n      purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the\n      demo to the public as is would introduce security risks for the host.\n    -->\n    <link rel=\"shortcut icon\" type=\"image/png\" href=\"/logo.png\">\n    <link rel=\"stylesheet\" href=\"/xterm.css\" />\n    <link rel=\"stylesheet\" href=\"/index.css\" />\n  </head>\n  <body>\n    <div id=\"banner\">\n      <span class=\"banner-title\">xterm.js</span>\n      <div class=\"banner-tabs\"></div>\n    </div>\n    <div id=\"container\">\n      <div class=\"grid\">\n        <div id=\"terminal-container\"></div>\n      </div>\n      <div id=\"sidebar\" class=\"grid\">\n      </div>\n    </div>\n      <script src=\"dist/client-bundle.js\" defer ></script>\n    </body>\n</html>\n"
  },
  {
    "path": "demo/server/server.ts",
    "content": "/**\n * WARNING: This demo is a barebones implementation designed for development and evaluation\n * purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the\n * demo to the public as is would introduce security risks for the host.\n */\n\nimport express from 'express';\nimport expressWs from 'express-ws';\nimport * as os from 'os';\nimport * as pty from 'node-pty';\nimport * as path from 'path';\nimport type { IPty } from 'node-pty';\n\ninterface IDisposable {\n  dispose(): void;\n}\n\n/** Whether to use binary transport. */\nconst USE_BINARY = os.platform() !== 'win32';\n\nconst demoRoot = path.join(__dirname, '..');\n\nfunction startServer(): void {\n  const app = express();\n  const appWs = expressWs(app).app;\n\n  const terminals: { [pid: number]: IPty } = {};\n  const unsentOutput: { [pid: number]: string } = {};\n  const temporaryDisposable: { [pid: number]: IDisposable } = {};\n\n  app.use('/xterm.css', express.static(demoRoot + '/../css/xterm.css'));\n  app.get('/logo.png', (req, res) => {\n    res.sendFile(demoRoot + '/logo.png');\n  });\n\n  app.get('/', (req, res) => {\n    res.sendFile(demoRoot + '/index.html');\n  });\n\n  app.get('/test', (req, res) => {\n    res.sendFile(demoRoot + '/test.html');\n  });\n\n  app.get('/index.css', (req, res) => {\n    res.sendFile(demoRoot + '/index.css');\n  });\n\n  app.use('/fonts', express.static(demoRoot + '/fonts'));\n\n  app.use('/dist', express.static(demoRoot + '/dist'));\n  app.use('/src', express.static(demoRoot + '/src'));\n\n  app.post('/terminals', (req, res) => {\n    const env: { [key: string]: string } = {};\n    for (const k of Object.keys(process.env)) {\n      const v = process.env[k];\n      if (v) {\n        env[k] = v;\n      }\n    }\n    env['COLORTERM'] = 'truecolor';\n    if (typeof req.query.cols !== 'string' || typeof req.query.rows !== 'string') {\n      console.error({ req });\n      throw new Error('Unexpected query args');\n    }\n    const cols = parseInt(req.query.cols);\n    const rows = parseInt(req.query.rows);\n    const pixelWidth = typeof req.query.pixelWidth === 'string' ? parseInt(req.query.pixelWidth) : 0;\n    const pixelHeight = typeof req.query.pixelHeight === 'string' ? parseInt(req.query.pixelHeight) : 0;\n    const isWindows = process.platform === 'win32';\n    const term = pty.spawn(isWindows ? 'powershell.exe' : 'bash', [], {\n      name: 'xterm-256color',\n      cols: cols ?? 80,\n      rows: rows ?? 24,\n      cwd: isWindows ? undefined : env.PWD,\n      env,\n      encoding: USE_BINARY ? null : 'utf8',\n      useConpty: isWindows,\n      useConptyDll: isWindows,\n    });\n\n    // Set pixel dimensions immediately after spawn (pty.spawn doesn't support them)\n    if (pixelWidth > 0 && pixelHeight > 0) {\n      term.resize(cols, rows, { width: pixelWidth, height: pixelHeight });\n      console.log('Created terminal with PID: ' + term.pid + ' (' + cols + 'x' + rows + ', ' + pixelWidth + 'px x ' + pixelHeight + 'px)');\n    } else {\n      console.log('Created terminal with PID: ' + term.pid);\n    }\n    terminals[term.pid] = term;\n    unsentOutput[term.pid] = '';\n    temporaryDisposable[term.pid] = term.onData(function(data) {\n      unsentOutput[term.pid] += data;\n    });\n    res.send(term.pid.toString());\n    res.end();\n  });\n\n  app.post('/terminals/:pid/size', (req, res) => {\n    if (typeof req.query.cols !== 'string' || typeof req.query.rows !== 'string') {\n      console.error({ req });\n      throw new Error('Unexpected query args');\n    }\n    const pid = parseInt(req.params.pid);\n    const cols = parseInt(req.query.cols);\n    const rows = parseInt(req.query.rows);\n    const pixelWidth = typeof req.query.pixelWidth === 'string' ? parseInt(req.query.pixelWidth) : 0;\n    const pixelHeight = typeof req.query.pixelHeight === 'string' ? parseInt(req.query.pixelHeight) : 0;\n    const term = terminals[pid];\n\n    if (pixelWidth > 0 && pixelHeight > 0) {\n      term.resize(cols, rows, { width: pixelWidth, height: pixelHeight });\n      console.log('Resized terminal ' + pid + ' to ' + cols + ' cols, ' + rows + ' rows, ' + pixelWidth + 'px x ' + pixelHeight + 'px');\n    } else {\n      term.resize(cols, rows);\n      console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');\n    }\n    res.end();\n  });\n\n  appWs.ws('/terminals/:pid', function (ws, req) {\n    const term = terminals[parseInt(req.params.pid)];\n    console.log('Connected to terminal ' + term.pid);\n    temporaryDisposable[term.pid].dispose();\n    delete temporaryDisposable[term.pid];\n    ws.send(unsentOutput[term.pid]);\n    delete unsentOutput[term.pid];\n\n    // unbuffered delivery after user input\n    let userInput = false;\n\n    // string message buffering\n    function buffer(socket: typeof ws, timeout: number, maxSize: number) {\n      let s = '';\n      let sender: ReturnType<typeof setTimeout> | null = null;\n      return (data: string) => {\n        s += data;\n        if (s.length > maxSize || userInput) {\n          userInput = false;\n          socket.send(s);\n          s = '';\n          if (sender) {\n            clearTimeout(sender);\n            sender = null;\n          }\n        } else if (!sender) {\n          sender = setTimeout(() => {\n            socket.send(s);\n            s = '';\n            sender = null;\n          }, timeout);\n        }\n      };\n    }\n    // binary message buffering\n    function bufferUtf8(socket: typeof ws, timeout: number, maxSize: number) {\n      const chunks: Buffer[] = [];\n      let length = 0;\n      let sender: ReturnType<typeof setTimeout> | null = null;\n      return (data: Buffer) => {\n        chunks.push(data);\n        length += data.length;\n        if (length > maxSize || userInput) {\n          userInput = false;\n          socket.send(Buffer.concat(chunks));\n          chunks.length = 0;\n          length = 0;\n          if (sender) {\n            clearTimeout(sender);\n            sender = null;\n          }\n        } else if (!sender) {\n          sender = setTimeout(() => {\n            socket.send(Buffer.concat(chunks));\n            chunks.length = 0;\n            length = 0;\n            sender = null;\n          }, timeout);\n        }\n      };\n    }\n    const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 3, 262144);\n\n    // WARNING: This is a naive implementation that will not throttle the flow of data. This means\n    // it could flood the communication channel and make the terminal unresponsive. Learn more about\n    // the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/\n    term.onData(function(data) {\n      try {\n        send(data as string & Buffer);\n      } catch {\n        // The WebSocket is not open, ignore\n      }\n    });\n    ws.on('message', function(msg) {\n      term.write(msg.toString());\n      userInput = true;\n    });\n    ws.on('close', function () {\n      term.kill();\n      console.log('Closed terminal ' + term.pid);\n      // Clean things up\n      delete terminals[term.pid];\n    });\n  });\n\n  const port = parseInt(process.env.PORT ?? '3000');\n  const host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';\n\n  console.log('App listening to http://127.0.0.1:' + port);\n  app.listen(port, host, 0);\n}\n\n// HACK: There is an EPIPE error thrown when reloading a page. This only seems to happen on Windows\n// and it's unclear why it happens. Suppressing the error here since this is just the demo server.\nprocess.on('uncaughtException', (error) => {\n  if (process.platform === 'win32' && error.message === 'read EPIPE') {\n    return;\n  }\n});\n\nexport default startServer;\n"
  },
  {
    "path": "demo/server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"outDir\": \"../out-demo/server\",\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\n    \"./**/*\",\n  ]\n}\n"
  },
  {
    "path": "demo/start.js",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// @ts-check\n\nconst startServer = require('./dist/server-bundle.js').default;\n\nstartServer();\n"
  },
  {
    "path": "demo/test.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>xterm.js integration test fixture</title>\n    <link rel=\"stylesheet\" href=\"/xterm.css\" />\n    <style>\n      body {\n        font-family: helvetica, sans-serif, arial;\n        font-size: 1em;\n        color: #111;\n      }\n      #terminal-container {\n        height: 60%;\n        margin: 0 auto;\n        padding: 2px;\n      }\n    </style>\n  </head>\n  <body id=\"test\">\n    <div id=\"terminal-container\"></div>\n    <script src=\"/dist/client-bundle.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "demo/tsconfig.json",
    "content": "{\n  \"references\": [\n    { \"path\": \"./client\" },\n    { \"path\": \"./server\" }\n  ],\n  \"files\": []\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "// @ts-check\nimport eslint from '@eslint/js';\nimport stylistic from '@stylistic/eslint-plugin';\nimport jsdoc from 'eslint-plugin-jsdoc';\nimport tseslint from 'typescript-eslint';\n\nexport default tseslint.config(\n  eslint.configs.recommended,\n  ...tseslint.configs.recommended,\n  {\n    ignores: [\n      'addons/*/src/third-party/*.ts',\n      '**/out/*',\n      '**/out-test/*',\n      '**/out-esbuild/*',\n      '**/out-esbuild-test/*',\n      '**/inwasm-sdks/*',\n      '**/typings/*.d.ts',\n      '**/node_modules',\n      '**/*.js',\n      '**/*.mjs'\n    ]\n  },\n  {\n    files: ['**/*.ts'],\n    plugins: {\n      '@stylistic': stylistic,\n      jsdoc\n    },\n    languageOptions: {\n      parserOptions: {\n        projectService: true,\n        tsconfigRootDir: import.meta.dirname\n      }\n    },\n    rules: {\n      '@stylistic/comma-spacing': ['warn', { before: false, after: true }],\n      '@stylistic/indent': ['warn', 2],\n      '@stylistic/semi': ['warn', 'always'],\n      '@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],\n      '@stylistic/member-delimiter-style': ['warn', {\n        multiline: { delimiter: 'semi', requireLast: true },\n        singleline: { delimiter: 'comma', requireLast: false }\n      }],\n      '@stylistic/type-annotation-spacing': 'warn',\n\n      '@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],\n      '@typescript-eslint/consistent-type-assertions': 'warn',\n      '@typescript-eslint/consistent-type-definitions': 'warn',\n      '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],\n      '@typescript-eslint/explicit-member-accessibility': ['warn', { accessibility: 'explicit', overrides: { constructors: 'off' } }],\n      '@typescript-eslint/naming-convention': [\n        'warn',\n        { selector: 'default', format: ['camelCase'], filter: { regex: '^[a-z]', match: true } },\n        { selector: 'variable', format: ['camelCase', 'UPPER_CASE'] },\n        { selector: 'variable', filter: '^I.+Service$', format: ['PascalCase'], prefix: ['I'] },\n        { selector: 'memberLike', modifiers: ['private'], format: ['camelCase'], leadingUnderscore: 'require' },\n        { selector: 'memberLike', modifiers: ['protected'], format: ['camelCase'], leadingUnderscore: 'require' },\n        { selector: 'enumMember', format: ['UPPER_CASE'] },\n        { selector: 'property', modifiers: ['public'], format: ['camelCase', 'UPPER_CASE'], filter: { regex: '^[a-z]', match: true } },\n        { selector: 'method', modifiers: ['public'], format: ['camelCase', 'UPPER_CASE'], custom: { regex: '^on[A-Z].+', match: false } },\n        { selector: 'method', modifiers: ['private'], format: ['camelCase'], leadingUnderscore: 'require', custom: { regex: '^on[A-Z].+', match: false } },\n        { selector: 'method', modifiers: ['protected'], format: ['camelCase'], leadingUnderscore: 'require', custom: { regex: '^on[A-Z].+', match: false } },\n        { selector: 'typeLike', format: ['PascalCase'] },\n        { selector: 'interface', format: ['PascalCase'], prefix: ['I'] }\n      ],\n      '@typescript-eslint/no-confusing-void-expression': ['warn', { ignoreArrowShorthand: true }],\n      '@typescript-eslint/no-useless-constructor': 'warn',\n      '@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true }],\n      '@typescript-eslint/prefer-namespace-keyword': 'warn',\n      '@typescript-eslint/no-unused-vars': ['warn', { vars: 'all', args: 'none' }],\n      '@typescript-eslint/no-require-imports': 'off',\n      // Added in eslint upgrade, new defaults\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-wrapper-object-types': 'off',\n      '@typescript-eslint/no-empty-object-type': 'off',\n      '@typescript-eslint/no-unsafe-function-type': 'off',\n      '@typescript-eslint/no-unused-expressions': 'off',\n      '@typescript-eslint/no-this-alias': 'off',\n      '@typescript-eslint/no-namespace': 'off',\n      // Allow duplicates for bit field constants\n      '@typescript-eslint/no-duplicate-enum-values': 'off',\n\n      'curly': ['warn', 'multi-line'],\n      'eqeqeq': ['warn', 'always'],\n      'jsdoc/check-alignment': 'warn',\n      'jsdoc/check-param-names': 'warn',\n      'jsdoc/no-multi-asterisks': 'warn',\n      'keyword-spacing': 'warn',\n      'max-len': ['warn', {\n        code: 1000,\n        comments: 100,\n        ignoreTrailingComments: true,\n        ignoreUrls: true,\n        ignorePattern: '^ *((?<vt_comment>(//|\\\\*) @vt)|(?<table_comment>\\\\* \\\\| )|(?<commented_code>//  ))'\n      }],\n      'new-parens': 'warn',\n      'no-duplicate-imports': 'warn',\n      'no-else-return': ['warn', { allowElseIf: false }],\n      'no-eval': 'warn',\n      'no-extra-semi': 'error',\n      'no-irregular-whitespace': 'warn',\n      'no-restricted-imports': ['warn', { patterns: ['.*\\\\/out\\\\/.*'] }],\n      'no-restricted-syntax': [\n        'warn',\n        { selector: \"CallExpression[callee.name='requestAnimationFrame']\", message: 'The global requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService.' },\n        { selector: \"CallExpression[callee.name='cancelAnimationFrame']\", message: 'The global cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService.' },\n        { selector: \"CallExpression > MemberExpression[object.name='window'][property.name='requestAnimationFrame']\", message: 'window.requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService.' },\n        { selector: \"CallExpression > MemberExpression[object.name='window'][property.name='cancelAnimationFrame']\", message: 'window.cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService.' },\n        { selector: \"MemberExpression[object.name='window'][property.name='devicePixelRatio']\", message: 'window.devicePixelRatio should be avoided, get it from ICoreBrowserService.' }\n      ],\n      'no-trailing-spaces': 'warn',\n      'no-unsafe-finally': 'warn',\n      'no-unused-vars': 'off',\n      'no-var': 'warn',\n      'one-var': ['warn', 'never'],\n      'no-empty': 'off',\n      'no-empty-pattern': 'off',\n      'no-cond-assign': 'off',\n      'no-case-declarations': 'off',\n      'for-direction': 'off',\n      'no-prototype-builtins': 'off',\n      'no-useless-escape': 'off',\n      'no-self-assign': 'off',\n      'no-async-promise-executor': 'off',\n      'prefer-rest-params': 'off',\n      'no-control-regex': 'off',\n      'no-fallthrough': 'off',\n      'prefer-spread': 'off',\n      'object-curly-spacing': ['warn', 'always'],\n      'prefer-const': 'warn',\n      'spaced-comment': ['warn', 'always', { markers: ['/'], exceptions: ['-'] }]\n    }\n  },\n  {\n    files: ['**/*.api.ts', '**/*.test.ts'],\n    rules: {\n      'object-curly-spacing': 'off',\n      'max-len': 'off',\n      '@typescript-eslint/no-unused-vars': 'off',\n      '@typescript-eslint/explicit-function-return-type': 'off',\n      '@typescript-eslint/explicit-member-accessibility': 'off'\n    }\n  },\n  {\n    // Disable prefer-nullish-coalescing for files without strictNullChecks\n    files: ['demo/**/*.ts', '**/*.benchmark.ts'],\n    rules: {\n      '@typescript-eslint/prefer-nullish-coalescing': 'off'\n    }\n  }\n);\n"
  },
  {
    "path": "eslint.config.typings.mjs",
    "content": "// @ts-check\nimport stylistic from '@stylistic/eslint-plugin';\nimport jsdoc from 'eslint-plugin-jsdoc';\nimport tseslint from 'typescript-eslint';\n\nexport default tseslint.config(\n  {\n    files: ['typings/**/*.d.ts'],\n    plugins: {\n      '@stylistic': stylistic,\n      '@typescript-eslint': tseslint.plugin,\n      jsdoc\n    },\n    languageOptions: {\n      parser: tseslint.parser\n    },\n    rules: {\n      '@stylistic/indent': ['warn', 2],\n      '@stylistic/semi': ['warn', 'always'],\n      '@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],\n      '@stylistic/member-delimiter-style': ['warn', {\n        multiline: { delimiter: 'semi', requireLast: true },\n        singleline: { delimiter: 'comma', requireLast: false }\n      }],\n      '@stylistic/type-annotation-spacing': 'warn',\n\n      '@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],\n      '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],\n      '@typescript-eslint/naming-convention': [\n        'warn',\n        { selector: 'typeLike', format: ['PascalCase'] },\n        { selector: 'interface', format: ['PascalCase'], prefix: ['I'] }\n      ],\n      '@typescript-eslint/prefer-namespace-keyword': 'warn',\n\n      'comma-dangle': ['warn', { objects: 'never', arrays: 'never', functions: 'never' }],\n      'curly': ['warn', 'multi-line'],\n      'eol-last': 'warn',\n      'eqeqeq': ['warn', 'always'],\n      'jsdoc/check-alignment': 'warn',\n      'jsdoc/check-param-names': 'warn',\n      'keyword-spacing': 'warn',\n      'max-len': ['warn', {\n        code: 1000,\n        comments: 80,\n        ignoreUrls: true,\n        ignorePattern: '^ *(?<ps_description>\\\\* Ps=)'\n      }],\n      'no-extra-semi': 'error',\n      'no-irregular-whitespace': 'warn',\n      'no-trailing-spaces': 'warn',\n      'object-curly-spacing': ['warn', 'always'],\n      'spaced-comment': ['warn', 'always', { markers: ['/'], exceptions: ['-'] }]\n    }\n  }\n);\n"
  },
  {
    "path": "fixtures/escape_sequence_files/NOTES",
    "content": "All tests are made for 80x25 terminal. Make sure to run tests with 80x25.\n\nCreate .text files from xterm (expected output)\n- open xterm\n- resize xterm to 80x25\n- run `python run_tests.py`\n- copy & paste whole window output into editor\n- add 26th empty line (due to line handling in toString) - not a bug, a feature ;)\n- advance to next test with ^D\n\n\nKnown problems\n##############\n\n\nt0031-HBP:\n    - no documentation at all about CSIj found - skipping\n\nt0050-ICH:\n    - bug in xterm? (cant ICH last real char, always sticks to last col)\n    - text used from https://github.com/MarkLodato/vt100-parser/blob/master/test/t0050-ICH.text\n"
  },
  {
    "path": "fixtures/escape_sequence_files/run_tests.py",
    "content": "from glob import glob\nimport os\nimport sys\nimport termios\nimport atexit\n\nBASE_DIR = os.path.dirname(os.path.abspath(__file__))\n\n\ndef enable_echo(fd, enabled):\n    (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(fd)\n    if enabled:\n        lflag |= termios.ECHO\n    else:\n        lflag &= ~termios.ECHO\n    new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]\n    termios.tcsetattr(fd, termios.TCSANOW, new_attr)\n\natexit.register(enable_echo, sys.stdin.fileno(), True)\n\noutput = []\n\n\ndef log(append=False, *s):\n    if append:\n        output[-1] += ' ' + ' '.join(str(part) for part in s)\n    else:\n        output.append(' '.join(str(part) for part in s))\n\n\ndef reset_terminal():\n    sys.stdout.write('\\x1bc\\x1b[H')\n    sys.stdout.flush()\n\n\ndef test():\n    count = 0\n    passed = 0\n    for i, testfile in enumerate(sorted(glob(os.path.join(BASE_DIR, '*.in')))):\n        count += 1\n        log(False, os.path.basename(testfile))\n        reset_terminal()\n        with open(testfile) as test:\n            sys.stdout.write('\\x1b]0;%s\\x07' % os.path.basename(testfile))\n            sys.stdout.write(test.read()+'\\x1bt')\n            sys.stdout.flush()\n        with open(os.path.join(os.path.dirname(testfile),\n                               os.path.basename(testfile).split('.')[0]+'.text')) as expected:\n            terminal_output = sys.stdin.read()\n            if not terminal_output:\n                # we are in xterm\n                continue\n            if terminal_output != expected.read():\n                log(True, '\\x1b[31merror\\x1b[0m')\n                with open(os.path.join(os.path.dirname(testfile), 'output',\n                                       os.path.basename(testfile)), 'w') as t_out:\n                    t_out.write(terminal_output)\n            else:\n                passed += 1\n                log(True, '\\x1b[32mpass\\x1b[0m')\n    return count, passed\n\n\nif __name__ == '__main__':\n    enable_echo(sys.stdin.fileno(), False)\n    count, passed = test()\n    enable_echo(sys.stdin.fileno(), True)\n    reset_terminal()\n    for i in range(len(output)/2+1):\n        if not (i+1) % 25:\n            sys.stdin.read()\n        print ''.join(i.ljust(40) for i in output[i*2:i*2+2])\n    print '\\x1b[33mcoverage: %s/%s (%d%%) tests passed.\\x1b[0m' % (passed, count, passed*100/count)\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0001-all_printable.in",
    "content": " !\"#$%&'()*+,-./\n0123456789:;<=>?\n@ABCDEFGHIJKLMNO\nPQRSTUVWXYZ[\\]^_\n`abcdefghijklmno\npqrstuvwxyz{|}~\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0001-all_printable.text",
    "content": " !\"#$%&'()*+,-./\n0123456789:;<=>?\n@ABCDEFGHIJKLMNO\nPQRSTUVWXYZ[\\]^_\n`abcdefghijklmno\npqrstuvwxyz{|}~\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0002-history.in",
    "content": " \n!\n\"\n#\n$\n%\n&\n'\n(\n)\n*\n+\n,\n-\n.\n/\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n:\n;\n<\n=\n>\n?\n@\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n[\n\\\n]\n^\n_\n`\na\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n{\n|\n}\n~\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0002-history.text",
    "content": "g\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n{\n|\n}\n~\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0002j-simple_string.in",
    "content": "abcdefghijklmnopqrstuvwxyz0123456789"
  },
  {
    "path": "fixtures/escape_sequence_files/t0002j-simple_string.text",
    "content": "abcdefghijklmnopqrstuvwxyz0123456789\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0003-line_wrap.in",
    "content": "a\nab\nabc\nabcd\nabcde\nabcdef\nabcdefg\nabcdefgh\nabcdefghi\nabcdefghij\nabcdefghijk\nabcdefghijkl\nabcdefghijklm\nabcdefghijklmn\nabcdefghijklmno\nabcdefghijklmnop\nabcdefghijklmnopq\nabcdefghijklmnopqr\nabcdefghijklmnopqrs\nabcdefghijklmnopqrst\nabcdefghijklmnopqrstu\nabcdefghijklmnopqrstuv\nabcdefghijklmnopqrstuvw\nabcdefghijklmnopqrstuvwx\nabcdefghijklmnopqrstuvwxy\nabcdefghijklmnopqrstuvwxyz\nabcdefghijklmnopqrstuvwxyzA\nabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABC\nabcdefghijklmnopqrstuvwxyzABCD\nabcdefghijklmnopqrstuvwxyzABCDE\nabcdefghijklmnopqrstuvwxyzABCDEF\nabcdefghijklmnopqrstuvwxyzABCDEFG\nabcdefghijklmnopqrstuvwxyzABCDEFGH\nabcdefghijklmnopqrstuvwxyzABCDEFGHI\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJ\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJK\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKL\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ab\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abc\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdef\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijk\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrs\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrst\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0003-line_wrap.text",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ab\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abc\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdef\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijk\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nr\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nrs\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nrst\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0003j-LF.in",
    "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0003j-LF.text",
    "content": "3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0004-LF.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\na\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0004-LF.text",
    "content": "7\n8\n9\n0\na\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0004j-CR.in",
    "content": "1\rx\n 2\rx\n  3\rx\n   4\rx\n    5\rx\n                                                                              6\rx\n                                                                               7\rx"
  },
  {
    "path": "fixtures/escape_sequence_files/t0004j-CR.text",
    "content": "x\nx2\nx 3\nx  4\nx   5\nx                                                                             6\nx                                                                              7\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0005-CR.in",
    "content": "b\ra\r\n b\ra\r\n  b\ra\r\n   b\ra\r\n    b\ra\r\n     b\ra\r\n      b\ra\r\n       b\ra\r\n        b\ra\r\n         b\ra\r\n          b\ra\r\n           b\ra\r\n            b\ra\r\n             b\ra\r\n              b\ra\r\n               b\ra\r\n                b\ra\r\n                 b\ra\r\n                  b\ra\r\n                   b\ra\r\n                    b\ra\r\n                     b\ra\r\n                      b\ra\r\n                       b\ra\r\n                        b\ra\r\n                         b\ra\r\n                          b\ra\r\n                           b\ra\r\n                            b\ra\r\n                             b\ra\r\n                              b\ra\r\n                               b\ra\r\n                                b\ra\r\n                                 b\ra\r\n                                  b\ra\r\n                                   b\ra\r\n                                    b\ra\r\n                                     b\ra\r\n                                      b\ra\r\n                                       b\ra\r\n                                        b\ra\r\n                                         b\ra\r\n                                          b\ra\r\n                                           b\ra\r\n                                            b\ra\r\n                                             b\ra\r\n                                              b\ra\r\n                                               b\ra\r\n                                                b\ra\r\n                                                 b\ra\r\n                                                  b\ra\r\n                                                   b\ra\r\n                                                    b\ra\r\n                                                     b\ra\r\n                                                      b\ra\r\n                                                       b\ra\r\n                                                        b\ra\r\n                                                         b\ra\r\n                                                          b\ra\r\n                                                           b\ra\r\n                                                            b\ra\r\n                                                             b\ra\r\n                                                              b\ra\r\n                                                               b\ra\r\n                                                                b\ra\r\n                                                                 b\ra\r\n                                                                  b\ra\r\n                                                                   b\ra\r\n                                                                    b\ra\r\n                                                                     b\ra\r\n                                                                      b\ra\r\n                                                                       b\ra\r\n                                                                        b\ra\r\n                                                                         b\ra\r\n                                                                          b\ra\r\n                                                                           b\ra\r\n                                                                            b\ra\r\n                                                                             b\ra\r\n                                                                              b\ra\r\n                                                                               b\ra\r\n                                                                                b\ra\r\n                                                                                 b\ra\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0005-CR.text",
    "content": "a                                                           b\na                                                            b\na                                                             b\na                                                              b\na                                                               b\na                                                                b\na                                                                 b\na                                                                  b\na                                                                   b\na                                                                    b\na                                                                     b\na                                                                      b\na                                                                       b\na                                                                        b\na                                                                         b\na                                                                          b\na                                                                           b\na                                                                            b\na                                                                             b\na                                                                              b\n                                                                                \na\n                                                                                \nab\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0006-IND.in",
    "content": "a\u001bDb\u001bDc\u001bDd\u001bDe\u001bDf\u001bDg\u001bDh\u001bDi\u001bDj\u001bDk\u001bDl\u001bDm\u001bDn\u001bDo\u001bDp\u001bDq\u001bDr\u001bDs\u001bDt\u001bDu\u001bDv\u001bDw\u001bDx\u001bDy\u001bDz\u001bDA\u001bDB\u001bDC\u001bDD\u001bDE\u001bDF\u001bDG\u001bDH\u001bDI\u001bDJ\u001bDK\u001bDL\u001bDM\u001bDN\u001bDO\u001bDP\u001bDQ\u001bDR\u001bDS\u001bDT\u001bDU\u001bDV\u001bDW\u001bDX\u001bDY\u001bDZ\u001bD0\u001bD1\u001bD2\u001bD3\u001bD4\u001bD5\u001bD6\u001bD7\u001bD8\u001bD9\u001bD0\u001bDa\u001bDb\u001bDc\u001bDd\u001bDe\u001bDf\u001bDg\u001bDh\u001bDi\u001bDj\u001bDk\u001bDl\u001bDm\u001bDn\u001bDo\u001bDp\u001bDq\u001bDr\u001bDs\u001bDt\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0006-IND.text",
    "content": "                                                           7\n                                                            8\n                                                             9\n                                                              0\n                                                               a\n                                                                b\n                                                                 c\n                                                                  d\n                                                                   e\n                                                                    f\n                                                                     g\n                                                                      h\n                                                                       i\n                                                                        j\n                                                                         k\n                                                                          l\n                                                                           m\n                                                                            n\n                                                                             o\n                                                                              p\n                                                                               q\n                                                                               r\n                                                                               s\n                                                                               t\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0007-space_at_end.in",
    "content": "0 space:\n1 space: \n2 space:  \n3 space:   \n70 space:                                                                      \n71 space:                                                                       \n72 space:                                                                        \n73 space:                                                                         \n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0007-space_at_end.text",
    "content": "0 space:\n1 space: \n2 space:  \n3 space:   \n70 space:                                                                      \n71 space:                                                                       \n72 space:                                                                       \n \n73 space:                                                                       \n  \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0008-BS.in",
    "content": "abcdefghijklmnopqrstuvwxyz\b\b\b\b\b\b!\nabc\b\b\b\b\b\b@\n\b#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\b\b\b\b\b\b$\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\b\b\b\b\b\b%\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\b\b\b\b\b\b^\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrs\b\b\b\b\b\b&\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0008-BS.text",
    "content": "abcdefghijklmnopqrst!vwxyz\n@bc\n#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij$lmnop\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij%lmnopq\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\n^\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\n&s\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0009-NEL.in",
    "content": "a\u001bEab\u001bEabc\u001bEabcd\u001bEabcde\u001bEabcdef\u001bEabcdefg\u001bEabcdefgh\u001bEabcdefghi\u001bEabcdefghij\u001bEabcdefghijk\u001bEabcdefghijkl\u001bEabcdefghijklm\u001bEabcdefghijklmn\u001bEabcdefghijklmno\u001bEabcdefghijklmnop\u001bEabcdefghijklmnopq\u001bEabcdefghijklmnopqr\u001bEabcdefghijklmnopqrs\u001bEabcdefghijklmnopqrst\u001bEabcdefghijklmnopqrstu\u001bEabcdefghijklmnopqrstuv\u001bEabcdefghijklmnopqrstuvw\u001bEabcdefghijklmnopqrstuvwx\u001bEabcdefghijklmnopqrstuvwxy\u001bEabcdefghijklmnopqrstuvwxyz\u001bEabcdefghijklmnopqrstuvwxyzA\u001bEabcdefghijklmnopqrstuvwxyzAB\u001bEabcdefghijklmnopqrstuvwxyzABC\u001bEabcdefghijklmnopqrstuvwxyzABCD\u001bEabcdefghijklmnopqrstuvwxyzABCDE\u001bEabcdefghijklmnopqrstuvwxyzABCDEF\u001bEabcdefghijklmnopqrstuvwxyzABCDEFG\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGH\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHI\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJ\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJK\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKL\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ab\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abc\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdef\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijk\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijkl\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrs\u001bEabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrst\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0009-NEL.text",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ab\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abc\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdef\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijk\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nr\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nrs\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nrst\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0010-RI.in",
    "content": "a\nb\nc\nd\u001bMe\u001bMf\u001bMg\nh\ni\nj....................................................................k\u001bMl\u001bMm\u001bMn\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0010-RI.text",
    "content": "a  g                                                                    n\nh f                                                                    m\nie                                                                    l\nj....................................................................k\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0011-RI_scroll.in",
    "content": "And the third.\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis should be the last line.\r\nThis one should be lost.\r\nThis one's a goner, too.\r\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bMThis is second line.\r\u001bMThis should be the first line.\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0011-RI_scroll.text",
    "content": "This should be the first line.\nThis is second line.\nAnd the third.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis should be the last line.\nThis one should be lost.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0012-VT.in",
    "content": "a\u000bb\u000bc\u000bd\u000be\u000bf\u000bg\u000bh\u000bi\u000bj\u000bk\u000bl\u000bm\u000bn\u000bo\u000bp\u000bq\u000br\u000bs\u000bt\u000bu\u000bv\u000bw\u000bx\u000by\u000bz\u000bA\u000bB\u000bC\u000bD\u000bE\u000bF\u000bG\u000bH\u000bI\u000bJ\u000bK\u000bL\u000bM\u000bN\u000bO\u000bP\u000bQ\u000bR\u000bS\u000bT\u000bU\u000bV\u000bW\u000bX\u000bY\u000bZ\u000b0\u000b1\u000b2\u000b3\u000b4\u000b5\u000b6\u000b7\u000b8\u000b9\u000b0\u000ba\u000bb\u000bc\u000bd\u000be\u000bf\u000bg\u000bh\u000bi\u000bj\u000bk\u000bl\u000bm\u000bn\u000bo\u000bp\u000bq\u000br\u000bs\u000bt\r\u000b"
  },
  {
    "path": "fixtures/escape_sequence_files/t0012-VT.text",
    "content": "                                                           7\n                                                            8\n                                                             9\n                                                              0\n                                                               a\n                                                                b\n                                                                 c\n                                                                  d\n                                                                   e\n                                                                    f\n                                                                     g\n                                                                      h\n                                                                       i\n                                                                        j\n                                                                         k\n                                                                          l\n                                                                           m\n                                                                            n\n                                                                             o\n                                                                              p\n                                                                               q\n                                                                               r\n                                                                               s\n                                                                               t\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0013-FF.in",
    "content": "a\fb\fc\fd\fe\ff\fg\fh\fi\fj\fk\fl\fm\fn\fo\fp\fq\fr\fs\ft\fu\fv\fw\fx\fy\fz\fA\fB\fC\fD\fE\fF\fG\fH\fI\fJ\fK\fL\fM\fN\fO\fP\fQ\fR\fS\fT\fU\fV\fW\fX\fY\fZ\f0\f1\f2\f3\f4\f5\f6\f7\f8\f9\f0\fa\fb\fc\fd\fe\ff\fg\fh\fi\fj\fk\fl\fm\fn\fo\fp\fq\fr\fs\ft\r\f\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0013-FF.text",
    "content": "                                                            8\n                                                             9\n                                                              0\n                                                               a\n                                                                b\n                                                                 c\n                                                                  d\n                                                                   e\n                                                                    f\n                                                                     g\n                                                                      h\n                                                                       i\n                                                                        j\n                                                                         k\n                                                                          l\n                                                                           m\n                                                                            n\n                                                                             o\n                                                                              p\n                                                                               q\n                                                                               r\n                                                                               s\n                                                                               t\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0014-CAN.in",
    "content": "abcd\u0018Defgh\r\nabcd\u001b\u0018Defgh\r\nabcd\u001b!\u0018Defgh\r\nabcd\u001b!*\u0018Defgh\r\nabcd\u001b[\u0018Defgh\r\nabcd\u001b[!\u0018Defgh\r\nabcd\u001b[2\u0018Defgh\r\nabcd\u001b[*2;\u0018Defgh\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0014-CAN.text",
    "content": "abcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0015-SUB.in",
    "content": "abcd\u001aDefgh\r\nabcd\u001b\u001aDefgh\r\nabcd\u001b!\u001aDefgh\r\nabcd\u001b!*\u001aDefgh\r\nabcd\u001b[\u001aDefgh\r\nabcd\u001b[!\u001aDefgh\r\nabcd\u001b[2\u001aDefgh\r\nabcd\u001b[*2;\u001aDefgh\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0015-SUB.text",
    "content": "abcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\nabcdDefgh\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0016-SU.in",
    "content": "Hello\u001b[SGoodbye\n\nUp\u001b[3SDown\nx\n\u001b[2S\n-----------------------------------------------------------------------------\u001b[Sx\n------------------------------------------------------------------------------\u001b[Sx\n-------------------------------------------------------------------------------\u001b[Sx\n--------------------------------------------------------------------------------\u001b[Sx\n---------------------------------------------------------------------------------\u001b[Sx\n.............................................................................\u001b[S\bx\n..............................................................................\u001b[S\bx\n...............................................................................\u001b[S\bx\n................................................................................\u001b[S\bx\n.................................................................................\u001b[S\bx\n\u001b[30S\nThe End.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0016-SU.text",
    "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThe End.\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0017-SD.in",
    "content": "A\n B\n  C\n   D\n    E\n     F\n      G\n       H\n        I\n         J\n          K\n           L\n            M\n             N\n              O\n               P\n                Q\n                 R\n                  S\n                   T\n                    U\n                     V\n                      W\n                       X\u001b[3S\na\n b\n  c\n   d\n    e\u001b[3T\n     f\n      g\n       h\n------------------------------------------------------------------------------\u001b[T1\n\n\n-------------------------------------------------------------------------------\u001b[T2\n\n\n--------------------------------------------------------------------------------\u001b[T3\n\n\n---------------------------------------------------------------------------------\u001b[T4\n\n\n..............................................................................\u001b[T\b5\n\n\n...............................................................................\u001b[T\b6\n\n\n................................................................................\u001b[T\b7\n\n\n.................................................................................\u001b[T\b8\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0017-SD.text",
    "content": "\n\na\n b\n     f\n      g\n       h                                                                      1\n\n                                                                               2\n\n\n3\n\n\n-4------------------------------------------------------------------------------\n\n                                                                             5\n\n                                                                              6\n\n                                                                              7\n\n\n8...............................................................................\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0020-CUF.in",
    "content": "abcdefg\u001b[Chijkl\nabcdefg\u001b[10Chijkl\nabcdefg\u001b[10;3Chijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\u001b[C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001b[C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001b[C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\u001b[C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[3C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[4C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[5C@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[6C@\n\u001b[79Cx\n\u001b[80Cx\nabcd\u001b[10C\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0020-CUF.text",
    "content": "abcdefg hijkl\nabcdefg          hijkl\nabcdefg          hijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nr @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\n                                                                               x\n                                                                               x\nabcd\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0021-CUB.in",
    "content": "abcdefg\u001b[D!@\nabcdefg\u001b[10D!@\nabcdefg\u001b[2;3D!@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\u001b[D@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001b[D@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001b[D@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\u001b[D@\n\u001b[Dx\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0021-CUB.text",
    "content": "abcdef!@\n!@cdefg\nabcde!@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno@q\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\n@\nx\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0022-CUU.in",
    "content": "a\nb\nc\nd\u001b[Ae\u001b[Af\u001b[Ag\nh\ni\nj....................................................................k\u001b[Al\u001b[Am\u001b[An\n\n\n\n\n\n\n\n\n\n\n\n\u001b[0A0\u001b[1A1\u001b[2A2\u001b[3;5A3\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0022-CUU.text",
    "content": "a  g                                                                    n\nh f                                                                    m\nie                                                                    l\nj....................................................................k\n\n   3\n\n\n  2\n\n 1\n0\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0023-CUU_scroll.in",
    "content": "This is the first line.\r\nThis is the second line.\r\nAnd the third.\r\nThis line should be deleted.\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPenultimate line.\r\nThis should be the last line.\r\u001b[36AI have gone up all the way...\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0023-CUU_scroll.text",
    "content": "I have gone up all the way...\nThis line should be deleted.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPenultimate line.\nThis should be the last line.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0024-CUD.in",
    "content": "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np\r\nq\r\nr\r\ns\r\nt\r\nu\r\nv\r\nw\r\nx\u001b[23A0\u001b[B1\u001b[0B2\u001b[1B3\u001b[2B4\u001b[3;5B5\u001b[100BBottom line.\r\u001b[8A\u001b[78CA\u001b[BB\u001b[BC\u001b[BD\u001b[BE\r\u001b[6B\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0024-CUD.text",
    "content": "b 1\nc  2\nd   3\ne\nf    4\ng\nh\ni     5\nj\nk\nl\nm\nn\no\np\nq                                                                             A\nr                                                                              B\ns                                                                              C\nt                                                                              D\nu                                                                              E\nv\nw\nx\n       Bottom line.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0025-CUP.in",
    "content": "\u001b[Ha\u001b[2;3Hb\u001b[;4Hc\u001b[10;10Hd\u001b[5He\u001b[40;16Hf\u001b[20;100Hg\u001b[100;200H\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0025-CUP.text",
    "content": "  b\n\n\ne\n\n\n\n\n         d\n\n\n\n\n\n\n\n\n\n                                                                               g\n\n\n\n\n               f\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0026-CNL.in",
    "content": "abc\u001b[Edef\u001b[5Eghi\r\r\n-------------------------------------------------------------------------abcdefg\u001b[Ehij\u001b[100Elast line\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0026-CNL.text",
    "content": "def\n\n\n\n\nghi\n-------------------------------------------------------------------------abcdefg\nhij\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nlast line\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0027-CPL.in",
    "content": "erased\u001b[Freplacement\n\n\nline four\u001b[2Fline two\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0027-CPL.text",
    "content": "replacement\nline two\n\nline four\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0030-HPR.in",
    "content": "abcdefg\u001b[ahijkl\nabcdefg\u001b[10ahijkl\nabcdefg\u001b[10;3ahijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\u001b[a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001b[a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001b[a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\u001b[a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[3a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[4a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[5a@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm\u001b[6a@\n\u001b[79ax\n\u001b[80ax\nabcd\u001b[10a\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0030-HPR.text",
    "content": "abcdefg hijkl\nabcdefg          hijkl\nabcdefg          hijkl\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nr @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm   @\n                                                                               x\n                                                                               x\nabcd\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0031-HPB.in_",
    "content": "abcdefg\u001b[j!@\nabcdefg\u001b[10j!@\nabcdefg\u001b[2;3j!@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno\u001b[j@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001b[j@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001b[j@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr\u001b[j@\n\u001b[jx\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0031-HPB.text",
    "content": "abcdefg!@\nabcdefg!@\nabcdefg!@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\n@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\nr@\nx\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0032-VPB.in",
    "content": "a\nb\nc\nd\u001b[ke\u001b[kf\u001b[kg\nh\ni\nj....................................................................k\u001b[kl\u001b[km\u001b[kn\n\n\n\n\n\n\n\n\n\n\n\n\u001b[0k0\u001b[1k1\u001b[2k2\u001b[3;5k3\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0032-VPB.text",
    "content": "b\nc\ndefg\nh\ni\nj....................................................................klmn\n\n\n\n\n\n\n\n\n\n\n\n0123\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0033-VPB_scroll.in",
    "content": "This is the first line.\r\nThis is the second line.\r\nAnd the third.\r\nThis line should be deleted.\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPenultimate line.\r\nThis should be the last line.\r\u001b[36kI have gone up all the way...\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0033-VPB_scroll.text",
    "content": "I have gone up all the way...\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0034-VPR.in",
    "content": "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np\r\nq\r\nr\r\ns\r\nt\r\nu\r\nv\r\nw\r\nx\u001b[23A0\u001b[e1\u001b[0e2\u001b[1e3\u001b[2e4\u001b[3;5e5\u001b[100eBottom line.\r\u001b[8A\u001b[78CA\u001b[eB\u001b[eC\u001b[eD\u001b[eE\r\u001b[6e\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0034-VPR.text",
    "content": "b 1\nc  2\nd   3\ne\nf    4\ng\nh\ni     5\nj\nk\nl\nm\nn\no\np\nq                                                                             A\nr                                                                              B\ns                                                                              C\nt                                                                              D\nu                                                                              E\nv\nw\nx\n       Bottom line.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0035-HVP.in",
    "content": "\u001b[fa\u001b[2;3fb\u001b[;4fc\u001b[10;10fd\u001b[5fe\u001b[40;16ff\u001b[20;100fg\u001b[100;200f\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0035-HVP.text",
    "content": "  b\n\n\ne\n\n\n\n\n         d\n\n\n\n\n\n\n\n\n\n                                                                               g\n\n\n\n\n               f\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0040-REP.in",
    "content": "x\u001b[5b\n\u001b[3b<\nabcdefg\u001b[3D\u001b[b\nabcdefg\u001b[3D\u001b[b!\n                                                                       @\u001b[20b\n                                                                               .\u001b[4b\n                                                                               ?\u001b[0b-\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0040-REP.text",
    "content": "xxxxxx\n<\nabcdefg\nabcd!fg\n                                                                       @@@@@@@@@\n@@@@@@@@@@@@\n                                                                               .\n....\n                                                                               ?\n?-\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0050-ICH.in",
    "content": "abcdefghijklmnopqrstuvwxyz\b\b\b\b\b\u001b[15@\nabcdefghijklmnopqrstuvwxyz\b\b\b\b\b\u001b[80@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[17@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[18@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[19@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[20@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[21@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001b[5@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001b[5@\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[5@\nICH at end:\u001b[5@\n\nabcdefghijklmnopqrstuvwxyz\b\b\b\b\b\u001b[15@!@#\nabcdefghijklmnopqrstuvwxyz\b\b\b\b\b\u001b[80@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[17@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[18@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[19@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[20@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\b\b\u001b[21@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\u001b[5@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\u001b[5@!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[5@!@#\nICH at end:\u001b[5@!@#\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0050-ICH.text",
    "content": "abcdefghijklmnopqrstu\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567                 89\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567                  89\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567                   8\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW     XYZ01234567890abcdefghijkl\nICH at end:\n\nabcdefghijklmnopqrstu!@#            vwxyz\nabcdefghijklmnopqrstu!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567!@#              89\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567!@#               89\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567!@#                8\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567!@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop!\n@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop!\n@#\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW!@#  XYZ01234567890abcdefghijkl\nICH at end:!@#\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0051-IL.in",
    "content": "ab\r\ncd\r\nef\r\ngh\r\nij\r\nkl\r\nmn\r\nop\u001b[2A\u001b[LQR\u001b[3A\u001b[4LST\u001b[10B\r\n1\r\n2\r\n3\r\n4\r\n5\u001b[A------------------------------------------------------------------------------a\u001b[Lb\u001b[2B\r\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\u001b[4A\u001b[3L\u001b[100B\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0051-IL.text",
    "content": "\nef\ngh\nij\nQR\nkl\nmn\nop\n1\n2\n3\nb\n4------------------------------------------------------------------------------a\n5\n6\n7\n8\n9\n10\n\n\n\n11\n12\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0052-DL.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\u001b[4A\u001b[2Mijklmnop\r\n\n\n1\n2\n3\n4\u001b[2A\r\u001b[79C\u001b[Mx\r\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0052-DL.text",
    "content": "a\nb\nc\nijklmnop\ng\nh\n1\nx\n4\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0053-DCH.in",
    "content": "abcdefghijklmnopqrstuvwxyz\u001b[8D>\u001b[2P\nabcdefghijklmnopqrstuvwxyz\u001b[8D>\u001b[2P!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[80D>\u001b[10P!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D>\u001b[10P!\n------------------------------------------------------------------------------\u001b[P?\n-------------------------------------------------------------------------------\u001b[P?\n-------------------------------------------------------------------------------\u001b[P?\n---------------------------------------------------------------------------------\u001b[P?\n..............................................................................\u001b[P\n...............................................................................\u001b[P\n...............................................................................\u001b[P\n.................................................................................\u001b[P\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0053-DCH.text",
    "content": "abcdefghijklmnopqr>vwxyz\nabcdefghijklmnopqr>!wxyz\n>!mnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\nabcdefghijklmnopqrstuvwxyz0123456789ABC>!PQRSTUVWXYZ0123456789abcdefgh\n------------------------------------------------------------------------------?\n-------------------------------------------------------------------------------?\n-------------------------------------------------------------------------------?\n--------------------------------------------------------------------------------\n-?\n..............................................................................\n...............................................................................\n...............................................................................\n................................................................................\n.\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0054-ECH.in",
    "content": "abcdefghijklmnopqrstuvwxyz\u001b[8D>\u001b[2X\nabcdefghijklmnopqrstuvwxyz\u001b[8D>\u001b[2X!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[80D>\u001b[10X!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D>\u001b[10X!\n------------------------------------------------------------------------------\u001b[X?\n-------------------------------------------------------------------------------\u001b[X?\n-------------------------------------------------------------------------------\u001b[X?\n---------------------------------------------------------------------------------\u001b[X?\n..............................................................................\u001b[X\n...............................................................................\u001b[X\n...............................................................................\u001b[X\n.................................................................................\u001b[X\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0054-ECH.text",
    "content": "abcdefghijklmnopqr>  vwxyz\nabcdefghijklmnopqr>! vwxyz\n>!         lmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\nabcdefghijklmnopqrstuvwxyz0123456789ABC>!         OPQRSTUVWXYZ0123456789abcdefgh\n------------------------------------------------------------------------------?\n-------------------------------------------------------------------------------?\n-------------------------------------------------------------------------------?\n--------------------------------------------------------------------------------\n-?\n..............................................................................\n...............................................................................\n...............................................................................\n................................................................................\n.\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0055-EL.in",
    "content": "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[K\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[0K\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[1K\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[2K\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[K!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[1K!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[40D><\b\u001b[2K!\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\u001b[K!\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0055-EL.text",
    "content": "abcdefghijklmnopqrstuvwxyz0123456789ABC>\nabcdefghijklmnopqrstuvwxyz0123456789ABC>\n                                         FGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\n\nabcdefghijklmnopqrstuvwxyz0123456789ABC>!\n                                        !FGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh\n                                        !\nabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefg!\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0056-ED.in",
    "content": "a\nab\nabc\nabcd\nabcde\nabcdef\nabcdefg\nabcdefgh\nabcdefghi\nabcdefghij\nabcdefghijk\nabcdefghijkl\nabcdefghijklm\nabcdefghijklmn\nabcdefghijklmno\nabcdefghijklmnop\nabcdefghijklmnopq\nabcdefghijklmnopqr\nabcdefghijklmnopqrs\nabcdefghijklmnopqrst\nabcdefghijklmnopqrstu\nabcdefghijklmnopqrstuv\nabcdefghijklmnopqrstuvw\nabcdefghijklmnopqrstuvwx\nabcdefghijklmnopqrstuvwxy\nabcdefghijklmnopqrstuvwxyz\u001b[20D\u001b[5A\u001b[J\u001b[10A\u001b[1J\u001b[40B\nA\nAB\nABC\nABCD\nABCDE\nABCDEF\nABCDEFG\nABCDEFGH\nABCDEFGHI\nABCDEFGHIJ\nABCDEFGHIJK\nABCDEFGHIJKL\nABCDEFGHIJKLM\nABCDEFGHIJKLMN\nABCDEFGHIJKLMNO\nABCDEFGHIJKLMNOP\nABCDEFGHIJKLMNOPQ\nABCDEFGHIJKLMNOPQR\nABCDEFGHIJKLMNOPQRS\nABCDEFGHIJKLMNOPQRST\nABCDEFGHIJKLMNOPQRSTU\nABCDEFGHIJKLMNOPQRSTUV\nABCDEFGHIJKLMNOPQRSTUVW\nABCDEFGHIJKLMNOPQRSTUVWX\nABCDEFGHIJKLMNOPQRSTUVWXY\u001b[20D\u001b[2J!\n\u001b[30B\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\u001b[3Av\u001b[B\u001b[J^\n\n\n\nthe end\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0056-ED.text",
    "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n     !\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaav\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^\n\n\n\nthe end\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0057-ED3.in",
    "content": "a\nab\nabc\nabcd\nabcde\nabcdef\nabcdefg\nabcdefgh\nabcdefghi\nabcdefghij\nabcdefghijk\nabcdefghijkl\nabcdefghijklm\nabcdefghijklmn\nabcdefghijklmno\nabcdefghijklmnop\nabcdefghijklmnopq\nabcdefghijklmnopqr\nabcdefghijklmnopqrs\nabcdefghijklmnopqrst\nabcdefghijklmnopqrstu\nabcdefghijklmnopqrstuv\nabcdefghijklmnopqrstuvw\nabcdefghijklmnopqrstuvwx\nabcdefghijklmnopqrstuvwxy\nabcdefghijklmnopqrstuvwxyz\nA\nAB\nABC\nABCD\nABCDE\nABCDEF\nABCDEFG\nABCDEFGH\nABCDEFGHI\nABCDEFGHIJ\nABCDEFGHIJK\nABCDEFGHIJKL\nABCDEFGHIJKLM\nABCDEFGHIJKLMN\nABCDEFGHIJKLMNO\nABCDEFGHIJKLMNOP\nABCDEFGHIJKLMNOPQ\nABCDEFGHIJKLMNOPQR\nABCDEFGHIJKLMNOPQRS\nABCDEFGHIJKLMNOPQRST\nABCDEFGHIJKLMNOPQRSTU\nABCDEFGHIJKLMNOPQRSTUV\nABCDEFGHIJKLMNOPQRSTUVW\nABCDEFGHIJKLMNOPQRSTUVWX\nABCDEFGHIJKLMNOPQRSTUVWXY\u001b[3J\nthis\nis\nthe\nend\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0057-ED3.note",
    "content": "Xterm behaves oddly with CSI 3 J.  This function is supposed to clear the\nsaved lines in history.  Xterm does this, but a small number of lines of\nhistory are not cleared.  The number seems to vary with how high the window is\nand how much output has recently been saved.  There is no reason to simulate\nthis behavior, so the expected outputs are as if the entire history was\nerased.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0057-ED3.text",
    "content": "ABCDEF\nABCDEFG\nABCDEFGH\nABCDEFGHI\nABCDEFGHIJ\nABCDEFGHIJK\nABCDEFGHIJKL\nABCDEFGHIJKLM\nABCDEFGHIJKLMN\nABCDEFGHIJKLMNO\nABCDEFGHIJKLMNOP\nABCDEFGHIJKLMNOPQ\nABCDEFGHIJKLMNOPQR\nABCDEFGHIJKLMNOPQRS\nABCDEFGHIJKLMNOPQRST\nABCDEFGHIJKLMNOPQRSTU\nABCDEFGHIJKLMNOPQRSTUV\nABCDEFGHIJKLMNOPQRSTUVW\nABCDEFGHIJKLMNOPQRSTUVWX\nABCDEFGHIJKLMNOPQRSTUVWXY\nthis\nis\nthe\nend\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0060-DECSC.in",
    "content": "          \u001b7v    1\u001b[S\b2\n\n3\u001b8^\n\n\n\n                                                                               v\u001b7\n...\u001b[Sooo\u001b8^\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0060-DECSC.text",
    "content": "\n3\n\n                                                                               v\n...                                                                            ^\n   ooo\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0061-CSI_s.in",
    "content": "          \u001b[sv    1\u001b[S\b2\n\n3\u001b[u^\n\n\n\n                                                                               v\u001b[s\n...\u001b[Sooo\u001b[u^\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0061-CSI_s.text",
    "content": "\n3\n\n                                                                               v\n...                                                                            ^\n   ooo\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0070-DECSTBM_LF.in",
    "content": "\u001b[3;7r1\n2\n3\n4\n5\n6\n7\n8\n9\u001b[78GABCDEF\u001b[8da\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\u001b[78Gvwxyz\n\u001b[r\u001b[25d\nThe end.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0070-DECSTBM_LF.text",
    "content": "6\n7\n8\n9                                                                            ABC\nDEF\n   a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nyz                                                                           vwx\nThe end.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0071-DECSTBM_IND.in",
    "content": "\u001b[3;7r1\u001bD2\u001bD3\u001bD4\u001bD5\u001bD6\u001bD7\u001bD8\u001bD9\u001b[78GABCDEF\u001b[8da\u001bDb\u001bDc\u001bDd\u001bDe\u001bDf\u001bDg\u001bDh\u001bDi\u001bDj\u001bDk\u001bDl\u001bDm\u001bDn\u001bDo\u001bDp\u001bDq\u001bDr\u001bDs\u001bDt\u001bDu\u001b[78Gvwxyz\n\u001b[r\u001b[25d\nThe end.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0071-DECSTBM_IND.text",
    "content": "     6\n      7\n       8\n        9                                                                    ABC\nDEF\n   a\n    b\n     c\n      d\n       e\n        f\n         g\n          h\n           i\n            j\n             k\n              l\n               m\n                n\n                 o\n                  p\n                   q\nyz                  rstu                                                     vwx\nThe end.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0072-DECSTBM_NEL.in",
    "content": "\u001b[3;7r1\u001bE2\u001bE3\u001bE4\u001bE5\u001bE6\u001bE7\u001bE8\u001bE9\u001b[8da\u001bEb\u001bEc\u001bEd\u001bEe\u001bEf\u001bEg\u001bEh\u001bEi\u001bEj\u001bEk\u001bEl\u001bEm\u001bEn\u001bEo\u001bEp\u001bEq\u001bEr\u001bEs\u001bEt\u001bEu\u001b[r\u001b[24d\nThe end.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0072-DECSTBM_NEL.text",
    "content": "2\n5\n6\n7\n8\n9\n a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nThe end.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0073-DECSTBM_RI.in",
    "content": "TOP 1\r\nTOP 2\r\nTOP 3\r\nTOP 4\r\nTOP 5\r\nTOP 6 - GONE\r\n\n\n\n\n\n\n\n\n\n\n\n\nBOTTOM 6 - GONE\r\nBOTTOM 5\r\nBOTTOM 4\r\nBOTTOM 3\r\nBOTTOM 2\r\nBOTTOM 1\u001b[6;19r\n\n\n\n\nAnd the third.\r\n\n\n\n\n\n\n\n\n\n\nThis should be the last line.\r\nThis one should be lost.\r\nThis one's a goner, too.\r\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bM\u001bMThis is second line.\r\u001bMThis should be the first line.\u001b[r\u001b[24d\nThe end.\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0073-DECSTBM_RI.text",
    "content": "TOP 2\nTOP 3\nTOP 4\nTOP 5\nThis should be the first line.\nThis is second line.\nAnd the third.\n\n\n\n\n\n\n\n\n\n\nThis should be the last line.\nBOTTOM 5\nBOTTOM 4\nBOTTOM 3\nBOTTOM 2\nBOTTOM 1\nThe end.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\u001b[5;9r\u001b[S\u001b[14;22r\u001b[3T\u001b[r\u001b[24d\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text",
    "content": "a\nb\nc\nd\nf\ng\nh\ni\n\nj\nk\nl\nm\n\n\n\nn\no\np\nq\nr\ns\nw\nx\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0075-DECSTBM_CUU_CUD.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\u001b[10;19r\u001b[25B1\u001b[24d\u001b[25A2\u001b[r\u001b[24d\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0075-DECSTBM_CUU_CUD.text",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj2\nk\nl\nm\nn\no\np\nq\nr\n1\nt\nu\nv\nw\nx\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.in",
    "content": " 1  (IL on line 2, expect nothing)\n 2  (DL on line 22, expect nothing)\n 3  vvvv  IL on line 5, expected:  A_BC\n 4  A\n 5  B\n 6  C\n 7  D\n 8  ^^^^\n 9  vvvv  DL on line 11, expected:  ACD_\n10  A\n11  B\n12  C\n13  D\n14  ^^^^\n15  vvvv  IL on line 17, expected:  A_\n16  A\n17  B\n18  ^^^^\n19  vvvv  IL on line 21, expected:  A_\n20  A\n21  B\n22  ^^^^\n23  vvvv  IL on line 24, expected:  _A\n24  A\u001b[4;7r\n\u001b[L\u001b[22d\u001b[M\u001b[5d\u001b[L\u001b[10;13r\u001b[11d\u001b[M\u001b[16;17r\u001b[17d\u001b[L\u001b[20;21r\u001b[21d\u001b[M\u001b[r\u001b[24d\n25  B\n26  ^^^^\n27  vvvv  DL on line 28, expected:  B_\n28  A\n29  B\n30  ^^^^\n31\n32\u001b[16;17r\u001b[16d\u001b[L\u001b[20;21r\u001b[20d\u001b[M\u001b[r\u001b[24d\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text",
    "content": " 8  ^^^^\n 9  vvvv  DL on line 11, expected:  ACD_\n10  A\n12  C\n13  D\n\n14  ^^^^\n15  vvvv  IL on line 17, expected:  A_\n16  A\n\n18  ^^^^\n19  vvvv  IL on line 21, expected:  A_\n20  A\n\n22  ^^^^\n\n23  vvvv  IL on line 24, expected:  _A\n25  B\n26  ^^^^\n28  A\n\n29  B\n30  ^^^^\n31\n32\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0077-DECSTBM_quirks.in",
    "content": " 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\u001b[;4r\u001b[S\u001b[4;4HA\u001b[H\n\u001b[22r\u001b[S\u001b[24;5HB\u001b[H\n\u001b[6;7;8r\u001b[S\u001b[7;6HC\u001b[H\n\u001b[15;0r\u001b[S\u001b[24;7HD\u001b[H\n\u001b[10;10r\u001b[S\u001b[24;8HE\u001b[H\n\u001b[15;11r\u001b[S\u001b[24;9HF\u001b[H\n\u001b[23;28r\u001b[S\u001b[24;10HG\u001b[H\n\u001b[r\u001b[24d\nDone.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0077-DECSTBM_quirks.text",
    "content": " 3\n 4\n   A\n 5\n 7\n     C\n 8\n 9\n10\n11\n12\n13\n14\n18\n19\n20\n21\n23\n24\n    B\n      D\n        F\n         G\nDone.\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0078-DECSTBM_CPL_CNL.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\u001b[10;19r\u001b[25E1\u001b[24d\u001b[25F2\u001b[r\u001b[24d\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0078-DECSTBM_CPL_CNL.text",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\n2\nk\nl\nm\nn\no\np\nq\nr\n1\nt\nu\nv\nw\nx\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0079-DECSTBM_VPR.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\u001b[10;19r\u001b[H\u001b[25e1\u001b[r\u001b[24d\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0079-DECSTBM_VPR.text",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\n1\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0080-HT.in",
    "content": "a\tb\tc\td\te\tf\tg\th\ti\tj\tk\tl\n\t\t\t\tx\n1\t2\n  \u001b[A3\u001b[B\n\nTab before EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh\t@\ntab (2):abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg\t@\n\nTab at EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\t@\nwith clipping:\t\t\t\t\t\t\t4567890abcdefghi\t\u001bM\u001bD@\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0080-HT.text",
    "content": "a       b       c       d       e       f       g       h       i       j      k\nl\n                                x\n1 3     2\n\n\nTab before EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh@\ntab (2):abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg @\n\nTab at EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\n@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\n@\nwith clipping:                                                  4567890abcdefgh@\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0081-TBC.in",
    "content": "default:\n>\t1\t2\t3\t4\t5\t6\t7\t8\t9\t0\n\nclear non-existant:    \u001b[gx                    \u001b[gx\n>\t1\t2\t3\t4\t5\t6\t7\t8\t9\t0\n\nclear 2 and 4:  \u001b[gx               \u001b[gx\n>\t1\t3\t5\t6\t7\t8\t9\t0\n\nclear 0:\t\t\t\t\t\t\t\u001b[gx\n>\t1\t3\t5\t6\t7\t8\t9\t0\n\nclear after 0:\t\t\t\t\t\t\t.\u001b[gx\n>\t1\t3\t5\t6\t7\t8\t9\t0\n\nclear all:\u001b[3g\n>\t0done\n\nTBC at end with clipping:             ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\u001b[g\u001bM\u001bD!\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0081-TBC.text",
    "content": "default:\n>       1       2       3       4       5       6       7       8       9      0\n\nclear non-existant:    x                    x\n>       1       2       3       4       5       6       7       8       9      0\n\nclear 2 and 4:  x               x\n>       1               3               5       6       7       8       9      0\n\nclear 0:                                                                       x\n>       1               3               5       6       7       8       9      0\n\nclear after 0:                                                                 .\nx\n>       1               3               5       6       7       8       9      0\n\nclear all:\n>                                                                              0\ndone\n\nTBC at end with clipping:             ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd!\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0082-HTS.in",
    "content": "\t1\t2\t3\t4\nabcde\u001bHFghijklmnopqrstuv\u001bHWxyz\n\tH\t1\t2\tW\t3\t4\n\nHTS at end: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\u001bHfgh\n\tH\t1\t2\tW\t3\t4\t5\t6\t7\t8\t9\ta\tb\n\nHTS at end with clipping:             ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\u001bH\u001bM\u001bD!\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0082-HTS.text",
    "content": "        1       2       3       4\nabcdeFghijklmnopqrstuvWxyz\n     H  1       2     W 3       4\n\nHTS at end: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde\nfgh\n     H  1       2     W 3       4       5       6       7       8       9      a\nb\n\nHTS at end with clipping:             ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd!\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0083-CHT.in",
    "content": "a\u001b[Ib\u001b[Ic\u001b[Id\u001b[Ie\u001b[If\u001b[Ig\u001b[Ih\u001b[Ii\u001b[Ij\u001b[Ik\u001b[Il\n\u001b[I\u001b[I\u001b[I\u001b[Ix\n1\u001b[I2\n  \u001b[A3\u001b[B\n\nCHT before EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh\u001b[I@\ntab (2):abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg\u001b[I@\n\nCHT at EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\u001b[I@\nwith clipping:\t\t\t\t\t\t\t4567890abcdefghi\u001b[I\u001bM\u001bD@\n\na\u001b[4Ie\u001b[2Ig\n\u001b[4Ie\t\u001b[2Ih\n\u001b[100Ix\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0083-CHT.text",
    "content": "a       b       c       d       e       f       g       h       i       j      k\nl\n                                x\n1 3     2\n\n\nCHT before EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh@\ntab (2):abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg @\n\nCHT at EOL:\nno tab: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\n@\ntab:    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi\n@\nwith clipping:                                                  4567890abcdefgh@\n\na                               e               g\n                                e                       h\n                                                                               x\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0084-CBT.in",
    "content": "a\tb\tc\td\te\tf\tg\th\ti\tj\tk\n                                                                    <\u001b[ZI\u001b[Zi\u001b[2Zh\u001b[3Zf\u001b[10Za\n\u001b[Za\n\ndefault tab stops:\nnear end:\t\t\t\t\t\t\t\tabcdefg\u001b[Z!\nat end:\t\t\t\t\t\t\t\t\tabcdefgh\u001b[Z!\nat end (2):\t\t\t\t\t\t\t\tabcdefgh\u001b[2Z!\nat end with clipping:\t\t\t\t\t\t\tabcdefgh\u001b[Z\u001bM\u001bD!\nat end with clipping (2):\t\t\t\t\t\tabcdefgh\u001b[2Z\u001bM\u001bD!\n\nset tab stop at column 80:                                                     \u001bHv\nat end:\t\t\t\t\t\t\t\t\tabcdefgh\u001b[Z!\nat end (2):\t\t\t\t\t\t\t\tabcdefgh\u001b[2Z!\nat end with clipping:\t\t\t\t\t\t\tabcdefgh\u001b[Z\u001bM\u001bD!\nat end with clipping (2):\t\t\t\t\t\tabcdefgh\u001b[2Z\u001bM\u001bD!\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0084-CBT.text",
    "content": "a       b       c       d       e       f       g       h       i       j      k\na                                       f               h       i   <\na\n\ndefault tab stops:\nnear end:                                                               !bcdefg\nat end:                                                                 abcdefgh\n!\nat end (2):                                                             abcdefgh\n!\nat end with clipping:                                                   !bcdefgh\nat end with clipping (2):                                       !       abcdefgh\n\nset tab stop at column 80:                                                     v\nat end:                                                                 abcdefgh\n!\nat end (2):                                                             abcdefgh\n!\nat end with clipping:                                                   !bcdefgh\nat end with clipping (2):                                       !       abcdefgh\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0084-CBT.text-xterm",
    "content": "a       b       c       d       e       f       g       h       i       j      k\na                                       f               h       i   <\na\n\ndefault tab stops:\nnear end:                                                               !bcdefg\nat end:                                                                 abcdefgh\n!\nat end (2):                                                             abcdefgh\n!\nat end with clipping:                                                   !bcdefgh\nat end with clipping (2):                                       !       abcdefgh\n\nset tab stop at column 80:                                                     v\nat end:                                                                 abcdefgh\n!\nat end (2):                                                             abcdefgh\n!\nat end with clipping:                                                   !bcdefgh\nat end with clipping (2):                                       !       abcdefgh\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0090-alt_screen.in",
    "content": "a\u001b[?47h\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\u001b[?47lz\nA\u001b[?47h\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\u001b[?47lZ\n                                                                            --->\u001b[?47hhello\u001b[?47l<--\n\n                                                                            --->\u001b[?47h\u001b[?47l<---\n\nfin\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0090-alt_screen.text",
    "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n z\nAZ\n     <--                                                                    --->\n\n                                                                            --->\n<---\n\nfin\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0091-alt_screen_ED3.in",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n\u001b[?47h\none\ntwo\nthree\nfour\nfive\nsix\n\u001b[3J\n\u001b[?47l\n11\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0091-alt_screen_ED3.text",
    "content": "o\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n\n11\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0092-alt_screen_DECSC.in",
    "content": "a\n    b\u001b7\n         c\u001b[?47h\n\n >\u001b7\n\n    \u001b[5Sxxxxxxx\u001b8\u001b[?47l!\u001b8<\n\n\n\n\n\n\n\nThe end.\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0092-alt_screen_DECSC.text",
    "content": "a\n    b<\n         c\n\n  !\n\n\n\n\nThe end.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0100-IRM.in",
    "content": "-------- insert single '!' --------------------------------------------abcdefghi\njklmnop\u001b[1;75H\u001b[4h!\u001b[4l\n\n\n-------- insert 0-9, with wraparound ----------------------------------abcdefghi\njklmnop\u001b[4;75H\u001b[4h0123456789\u001b[4l\n\n-------- repeat 3 '!' -------------------------------------------------abcdefghi\njklmnop\u001b[7;75H\u001b[4h!\u001b[2b\u001b[4l\n\n\n-------- repeat 10 '!', with wraparound -------------------------------abcdefghi\njklmnop\u001b[10;75H\u001b[4h!\u001b[9b\u001b[4l\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0100-IRM.text",
    "content": "-------- insert single '!' --------------------------------------------abc!defgh\njklmnop\n\n-------- insert 0-9, with wraparound ----------------------------------abc012345\n6789jklmnop\n\n-------- repeat 3 '!' -------------------------------------------------abc!!!def\njklmnop\n\n-------- repeat 10 '!', with wraparound -------------------------------abc!!!!!!\n!!!!jklmnop\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0101-NLM.in",
    "content": "a\nb\nc\nd\u001b[20h\ne\nf\u000bg\fh\ni\rj\rk\u001b[20l\nl\nm\nn\u000bo\fp\r\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0101-NLM.text",
    "content": "a\nb\nc\nd\ne\nf\ng\nh\nk\nl\nm\nn\n o\n  p\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0102-DECAWM.in",
    "content": "-------- default: wraparound  ----------------------------------------------abcdefgh\u001b[?7h\n--------     set: wraparound  ----------------------------------------------abcdefgh\u001b[?7l\n--------   unset: no wraparound  -------------------------------------------abcdefgh\n   this should be immediately below \"no wraparound\"\u001b[?7h\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0102-DECAWM.text",
    "content": "-------- default: wraparound  ----------------------------------------------abcd\nefgh\n--------     set: wraparound  ----------------------------------------------abcd\nefgh\n--------   unset: no wraparound  -------------------------------------------abch\n   this should be immediately below \"no wraparound\"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0103-reverse_wrap.in",
    "content": "\u001b[?45ha\nb\b\bA\n\nc\b\b\b-B\n\nd\b\bC!\u001b[H\b\b\b\b\b\b\bthe endreally!\u001b[?45l\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0103-reverse_wrap.text",
    "content": "c                                                                              C\n!\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n                                                                         the end\nreally!\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0200-SGR.html",
    "content": "<pre>\nImplemented non-color attributes:\n\n 1: This is <span style=\"font-weight: bold\">bold</span>.\n\n 3: This is <span style=\"font-style: italic\">italic</span>.\n\n 4: This is <span style=\"text-decoration: underline\">underlined</span>.\n\n 5: This is <span style=\"text-decoration: blink\">slowly blinking</span>.\n\n 6: This is <span style=\"text-decoration: blink\">rapidly blinking</span>.\n\n 7: This is <span style=\"background-color: black; color: white\">inverse</span>.\n\n 8: This is <span style=\"visibility: hidden\">hidden</span>.\n\n 9: This is <span style=\"text-decoration: line-through\">struck out</span>.\n\n21: This is <span style=\"text-decoration: underline; border-bottom: 1px solid\">double underlined</span>.\n\n53: This is <span style=\"text-decoration: overline\">overlined</span>.\n\n\n\nUnimplemented non-color attributes:\n\n 2: weight:feint\n\n20: style:fraktur\n\n51: frame:box\n\n52: frame:circle\n</pre>\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0200-SGR.in_",
    "content": "Implemented non-color attributes:\n\n 1: This is \u001b[1mbold\u001b[m.\n\n 3: This is \u001b[3mitalic\u001b[m.\n\n 4: This is \u001b[4munderlined\u001b[m.\n\n 5: This is \u001b[5mslowly blinking\u001b[m.\n\n 6: This is \u001b[6mrapidly blinking\u001b[m.\n\n 7: This is \u001b[7minverse\u001b[m.\n\n 8: This is \u001b[8mhidden\u001b[m.\n\n 9: This is \u001b[9mstruck out\u001b[m.\n\n21: This is \u001b[21mdouble underlined\u001b[m.\n\n53: This is \u001b[53moverlined\u001b[m.\n\n\n\nUnimplemented non-color attributes:\n\n 2: weight:feint\n\n20: style:fraktur\n\n51: frame:box\n\n52: frame:circle\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0220-SGR_inverse.html",
    "content": "<pre>\nThis is <span style=\"background-color: black; color: white\">inverse text</span> with default fg and bg.\n<span style=\"color: #cd0000\">This is </span><span style=\"color: white; background-color: #cd0000\">inverse text</span><span style=\"color: #cd0000\"> with red fg and default bg.</span>\n<span style=\"background-color: #cd0000\">This is </span><span style=\"background-color: black; color: #cd0000\">inverse text</span><span style=\"background-color: #cd0000\"> with default fg and red bg.</span>\n<span style=\"background-color: #cd0000; color: #00cd00\">This is </span><span style=\"background-color: #00cd00; color: #cd0000\">inverse text</span><span style=\"background-color: #cd0000; color: #00cd00\"> with green fg and red bg.</span>\n</pre>\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0220-SGR_inverse.in_",
    "content": "This is \u001b[7minverse text\u001b[27m with default fg and bg.\n\u001b[31mThis is \u001b[7minverse text\u001b[27m with red fg and default bg.\u001b[m\n\u001b[41mThis is \u001b[7minverse text\u001b[27m with default fg and red bg.\u001b[m\n\u001b[32;41mThis is \u001b[7minverse text\u001b[27m with green fg and red bg.\u001b[m\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0300-vttest1.in",
    "content": "Test of autowrap, mixing control and print characters.\n\n\nThe left/right margins should have letters in order:\n\n\n\u001b[3;21r\u001b[?6h\u001b[19;1HA\u001b[19;80Ha\n\u001b[18;80HaB\u001b[19;80HB\b b\n\u001b[19;80HC\b\b\t\tc\u001b[19;2H\bC\n\u001b[19;80H\n\u001b[18;1HD\u001b[18;80Hd\u001b[19;1HE\u001b[19;80He\n\u001b[18;80HeF\u001b[19;80HF\b f\n\u001b[19;80HG\b\b\t\tg\u001b[19;2H\bG\n\u001b[19;80H\n\u001b[18;1HH\u001b[18;80Hh\u001b[19;1HI\u001b[19;80Hi\n\u001b[18;80HiJ\u001b[19;80HJ\b j\n\u001b[19;80HK\b\b\t\tk\u001b[19;2H\bK\n\u001b[19;80H\n\u001b[18;1HL\u001b[18;80Hl\u001b[19;1HM\u001b[19;80Hm\n\u001b[18;80HmN\u001b[19;80HN\b n\n\u001b[19;80HO\b\b\t\to\u001b[19;2H\bO\n\u001b[19;80H\n\u001b[18;1HP\u001b[18;80Hp\u001b[19;1HQ\u001b[19;80Hq\n\u001b[18;80HqR\u001b[19;80HR\b r\n\u001b[19;80HS\b\b\t\ts\u001b[19;2H\bS\n\u001b[19;80H\n\u001b[18;1HT\u001b[18;80Ht\u001b[19;1HU\u001b[19;80Hu\n\u001b[18;80HuV\u001b[19;80HV\b v\n\u001b[19;80HW\b\b\t\tw\u001b[19;2H\bW\n\u001b[19;80H\n\u001b[18;1HX\u001b[18;80Hx\u001b[19;1HY\u001b[19;80Hy\n\u001b[18;80HyZ\u001b[19;80HZ\b z\n\u001b[?6l\u001b[r\u001b[22;1HPush <RETURN>\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0300-vttest1.text",
    "content": "Test of autowrap, mixing control and print characters.\n\nI                                                                              i\nJ                                                                              j\nK                                                                              k\nL                                                                              l\nM                                                                              m\nN                                                                              n\nO                                                                              o\nP                                                                              p\nQ                                                                              q\nR                                                                              r\nS                                                                              s\nT                                                                              t\nU                                                                              u\nV                                                                              v\nW                                                                              w\nX                                                                              x\nY                                                                              y\nZ                                                                              z\n\nPush <RETURN>\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0500-bash_long_line.in",
    "content": "Script started on Fri 27 Mar 2009 08:53:29 PM EDT\ndircolors: /etc/DIR_COLORS: No such file or directory\r\n\u001b]0;mark@mark-desktop:~/vt100-to-html\u0007mark@mark-desktop:~/vt100-to-html$ bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST \rUVWXYZ1234567890abcdefghijklmnopqrstuv\u001b[A\b\b\bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV\u001b[1PWXYZ1234567890abcdefghijklmnopqrstuv\u001b[A\b\b\r\n\r\r\n\u001b]0;mark@mark-desktop:~/vt100-to-html\u0007mark@mark-desktop:~/vt100-to-html$ exit\r\n\nScript done on Fri 27 Mar 2009 08:53:33 PM EDT\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0500-bash_long_line.text",
    "content": "Script started on Fri 27 Mar 2009 08:53:29 PM EDT\ndircolors: /etc/DIR_COLORS: No such file or directory\nmark@mark-desktop:~/vt100-to-html$ cdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU\nVWXYZ1234567890abcdefghijklmnopqrstuv\nmark@mark-desktop:~/vt100-to-html$ exit\n\nScript done on Fri 27 Mar 2009 08:53:33 PM EDT\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0501-bash_ls.in",
    "content": "Script started on Sun 17 May 2009 06:20:41 PM EDT\ndircolors: /etc/DIR_COLORS: No such file or directory\n\u001b]0;mark@mark-desktop:~/vt100-to-html/scripts\u0007mark@mark-desktop:~/vt100-to-html/scripts$ ls\nbash.script\n\u001b]0;mark@mark-desktop:~/vt100-to-html/scripts\u0007mark@mark-desktop:~/vt100-to-html/scripts$ ls /\nbin    dev     initrd.img      lib64\t   opt\t selinux  usr\nboot   etc     initrd.img.old  lost+found  proc  srv\t  var\nboot2  home    lib\t       media\t   root  sys\t  vmlinuz\ncdrom  initrd  lib32\t       mnt\t   sbin  tmp\t  vmlinuz.old\n\u001b]0;mark@mark-desktop:~/vt100-to-html/scripts\u0007mark@mark-desktop:~/vt100-to-html/scripts$ exit\n\nScript done on Sun 17 May 2009 06:20:52 PM EDT\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0501-bash_ls.text",
    "content": "Script started on Sun 17 May 2009 06:20:41 PM EDT\ndircolors: /etc/DIR_COLORS: No such file or directory\nmark@mark-desktop:~/vt100-to-html/scripts$ ls\nbash.script\nmark@mark-desktop:~/vt100-to-html/scripts$ ls /\nbin    dev     initrd.img      lib64       opt   selinux  usr\nboot   etc     initrd.img.old  lost+found  proc  srv      var\nboot2  home    lib             media       root  sys      vmlinuz\ncdrom  initrd  lib32           mnt         sbin  tmp      vmlinuz.old\nmark@mark-desktop:~/vt100-to-html/scripts$ exit\n\nScript done on Sun 17 May 2009 06:20:52 PM EDT\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0502-bash_ls_color.in",
    "content": "Script started on Sun 17 May 2009 06:21:05 PM EDT\ndircolors: /etc/DIR_COLORS: No such file or directory\n\u001b]0;mark@mark-desktop:~/vt100-to-html/scripts\u0007mark@mark-desktop:~/vt100-to-html/scripts$ ls --color=auto /\n\u001b[00m\u001b[01;34mbin\u001b[00m    \u001b[01;34mdev\u001b[00m     \u001b[01;36minitrd.img\u001b[00m      \u001b[01;36mlib64\u001b[00m       \u001b[01;34mopt\u001b[00m   \u001b[01;34mselinux\u001b[00m  \u001b[01;34musr\u001b[00m\n\u001b[01;34mboot\u001b[00m   \u001b[01;34metc\u001b[00m     \u001b[01;36minitrd.img.old\u001b[00m  \u001b[01;34mlost+found\u001b[00m  \u001b[01;34mproc\u001b[00m  \u001b[01;34msrv\u001b[00m      \u001b[01;34mvar\u001b[00m\n\u001b[01;34mboot2\u001b[00m  \u001b[01;34mhome\u001b[00m    \u001b[01;34mlib\u001b[00m             \u001b[01;34mmedia\u001b[00m       \u001b[01;34mroot\u001b[00m  \u001b[01;34msys\u001b[00m      \u001b[01;36mvmlinuz\u001b[00m\n\u001b[01;36mcdrom\u001b[00m  \u001b[01;34minitrd\u001b[00m  \u001b[01;34mlib32\u001b[00m           \u001b[01;34mmnt\u001b[00m         \u001b[01;34msbin\u001b[00m  \u001b[30;42mtmp\u001b[00m      \u001b[01;36mvmlinuz.old\u001b[00m\n\u001b[m\u001b]0;mark@mark-desktop:~/vt100-to-html/scripts\u0007mark@mark-desktop:~/vt100-to-html/scripts$ exit\n\nScript done on Sun 17 May 2009 06:21:11 PM EDT\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0502-bash_ls_color.text",
    "content": "Script started on Sun 17 May 2009 06:21:05 PM EDT\ndircolors: /etc/DIR_COLORS: No such file or directory\nmark@mark-desktop:~/vt100-to-html/scripts$ ls --color=auto /\nbin    dev     initrd.img      lib64       opt   selinux  usr\nboot   etc     initrd.img.old  lost+found  proc  srv      var\nboot2  home    lib             media       root  sys      vmlinuz\ncdrom  initrd  lib32           mnt         sbin  tmp      vmlinuz.old\nmark@mark-desktop:~/vt100-to-html/scripts$ exit\n\nScript done on Sun 17 May 2009 06:21:11 PM EDT\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0503-zsh_ls_color.in",
    "content": "Script started on Sun 17 May 2009 06:21:21 PM EDT\n\u001b]2;mark-desktop - ~/vt100-to-html/scripts\u0007\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m                                                                               \r\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[1;32m$\u001b[0m \u001b[K\u001b[54C\u001b[1;34m~/vt100-to-html/scripts\u001b[0m\u001b[77Dl\bls /\r\r\n\u001b]2;mark-desktop - ls /\u0007\u001b[00m\u001b[01;34mbin\u001b[00m    \u001b[01;34mdev\u001b[00m     \u001b[01;36minitrd.img\u001b[00m      \u001b[01;36mlib64\u001b[00m       \u001b[01;34mopt\u001b[00m   \u001b[01;34mselinux\u001b[00m  \u001b[01;34musr\u001b[00m\r\n\u001b[01;34mboot\u001b[00m   \u001b[01;34metc\u001b[00m     \u001b[01;36minitrd.img.old\u001b[00m  \u001b[01;34mlost+found\u001b[00m  \u001b[01;34mproc\u001b[00m  \u001b[01;34msrv\u001b[00m      \u001b[01;34mvar\u001b[00m\r\n\u001b[01;34mboot2\u001b[00m  \u001b[01;34mhome\u001b[00m    \u001b[01;34mlib\u001b[00m             \u001b[01;34mmedia\u001b[00m       \u001b[01;34mroot\u001b[00m  \u001b[01;34msys\u001b[00m      \u001b[01;36mvmlinuz\u001b[00m\r\n\u001b[01;36mcdrom\u001b[00m  \u001b[01;34minitrd\u001b[00m  \u001b[01;34mlib32\u001b[00m           \u001b[01;34mmnt\u001b[00m         \u001b[01;34msbin\u001b[00m  \u001b[30;42mtmp\u001b[00m      \u001b[01;36mvmlinuz.old\u001b[00m\r\n\u001b[m\u001b]2;mark-desktop - ~/vt100-to-html/scripts\u0007\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m                                                                               \r\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[1;32m$\u001b[0m \u001b[K\u001b[54C\u001b[1;34m~/vt100-to-html/scripts\u001b[0m\u001b[77D\r\r\n\nScript done on Sun 17 May 2009 06:21:27 PM EDT\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0503-zsh_ls_color.text",
    "content": "Script started on Sun 17 May 2009 06:21:21 PM EDT\n$ ls /                                                  ~/vt100-to-html/scripts\nbin    dev     initrd.img      lib64       opt   selinux  usr\nboot   etc     initrd.img.old  lost+found  proc  srv      var\nboot2  home    lib             media       root  sys      vmlinuz\ncdrom  initrd  lib32           mnt         sbin  tmp      vmlinuz.old\n$                                                       ~/vt100-to-html/scripts\n\nScript done on Sun 17 May 2009 06:21:27 PM EDT\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0504-vim.in",
    "content": "Script started on Sun 15 Aug 2010 11:53:27 PM EDT\n\u001b]0;mark-desktop - ~/vt100-to-html/test\u0007\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m                                                                               \r\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[1;32m$\u001b[0m \u001b[K\u001b[57C\u001b[1;34m~/vt100-to-html/test\u001b[0m\u001b[77Dl\bls\r\r\n\u001b]0;mark-desktop - ls\u0007\u001b[0m\u001b[01;34mexpected_text\u001b[0m             t0016-SU.text          t0050-ICH.text\r\n\u001b[01;34minput\u001b[0m                     t0017-SD.in            t0051-IL.in\r\n\u001b[01;32mrun_all.py\u001b[0m                t0017-SD.text          t0051-IL.text\r\nt0001-all_printable.in    t0020-CUF.in           t0052-DL.in\r\nt0001-all_printable.text  t0020-CUF.text         t0052-DL.text\r\nt0002-history.in          t0021-CUB.in           t0053-DCH.in\r\nt0002-history.text        t0021-CUB.text         t0053-DCH.text\r\nt0003-line_wrap.in        t0022-CUU.in           t0054-ECH.in\r\nt0003-line_wrap.text      t0022-CUU.text         t0054-ECH.text\r\nt0004-LF.in               t0023-CUU_scroll.in    t0055-EL.in\r\nt0004-LF.text             t0023-CUU_scroll.text  t0055-EL.text\r\nt0005-CR.in               t0024-CUD.in           t0056-ED.in\r\nt0005-CR.text             t0024-CUD.text         t0056-ED.text\r\nt0006-IND.in              t0025-CUP.in           t0057-ED3.in\r\nt0006-IND.text            t0025-CUP.text         t0057-ED3.note\r\nt0007-space_at_end.in     t0026-CNL.in           t0057-ED3.text\r\nt0007-space_at_end.text   t0026-CNL.text         t0060-DECSC.in\r\nt0008-BS.in               t0027-CPL.in           t0060-DECSC.text\r\nt0008-BS.text             t0027-CPL.text         t0061-CSI_s.in\r\nt0009-NEL.in              t0030-HPR.in           t0061-CSI_s.text\r\nt0009-NEL.text            t0030-HPR.text         t008x-alt_screen_ED.in\r\nt0010-RI.in               t0031-HPB.in           t008x-IRM.in\r\nt0010-RI.text             t0031-HPB.text         t008x-NLM.in\r\nt0011-RI_scroll.in        t0032-VPB.in           t008x-save_cursor_mode.in\r\nt0011-RI_scroll.text      t0032-VPB.text         t0500-bash_long_line.in\r\nt0012-VT.in               t0033-VPB_scroll.in    t0500-bash_long_line.text\r\nt0012-VT.text             t0033-VPB_scroll.text  t0501-bash_ls.in\r\nt0013-FF.in               t0034-VPR.in           t0501-bash_ls.text\r\nt0013-FF.text             t0034-VPR.text         t0502-bash_ls_color.in\r\nt0014-CAN.in              t0035-HVP.in           t0502-bash_ls_color.text\r\nt0014-CAN.text            t0035-HVP.text         t0503-zsh_ls_color.in\r\nt0015-SUB.in              t0040-REP.in           t0503-zsh_ls_color.text\r\nt0015-SUB.text            t0040-REP.text         typescript\r\nt0016-SU.in               t0050-ICH.in\r\n\u001b[m\u001b]0;mark-desktop - ~/vt100-to-html/test\u0007\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m                                                                               \r\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[1;32m$\u001b[0m \u001b[K\u001b[57C\u001b[1;34m~/vt100-to-html/test\u001b[0m\u001b[77Dv\bvim\r\r\n\u001b]0;mark-desktop - vim\u0007\u001b[?1000h\u001b[?1049h\u001b[?1h\u001b=\u001b[1;24r\u001b[?12;25h\u001b[?12l\u001b[?25h\u001b[27m\u001b[m\u001b[H\u001b[2J\u001b[>c\u001b[?25l\u001b[2;1H\u001b[38;5;12m~                                                                               \u001b[3;1H~                                                                               \u001b[4;1H~                                                                               \u001b[5;1H~                                                                               \u001b[6;1H~                                                                               \u001b[7;1H~                                                                               \u001b[8;1H~                                                                               \u001b[9;1H~                                                                               \u001b[10;1H~                                                                               \u001b[11;1H~                                                                               \u001b[12;1H~                                                                               \u001b[13;1H~                                                                               \u001b[14;1H~                                                                               \u001b[15;1H~                                                                               \u001b[16;1H~                                                                               \u001b[17;1H~                                                                               \u001b[18;1H~                                                                               \u001b[19;1H~                                                                               \u001b[20;1H~                                                                               \u001b[21;1H~                                                                               \u001b[22;1H~                                                                               \u001b[m\u001b[23;1H\u001b[1m\u001b[7m\u001b[38;5;68m[No Name]                                                     0,0-1          All\u001b[m\u001b[6;32HVIM - Vi IMproved\u001b[8;33Hversion 7.2.267\u001b[9;29Hby Bram Moolenaar et al.\u001b[10;19HVim is open source and freely distributable\u001b[12;26HBecome a registered Vim user!\u001b[13;18Htype  :help register\u001b[38;5;81m<Enter>\u001b[m   for information \u001b[15;18Htype  :q\u001b[38;5;81m<Enter>\u001b[m               to exit         \u001b[16;18Htype  :help\u001b[38;5;81m<Enter>\u001b[m  or  \u001b[38;5;81m<F1>\u001b[m  for on-line help\u001b[17;18Htype  :help version7\u001b[38;5;81m<Enter>\u001b[m   for version info\u001b]2;[No Name] - VIM\u0007\u001b]1;[No Name]\u0007\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?1000l\u001b[?1002h\u001bP+q436f\u001b\\\u001bP+q6b75\u001b\\\u001bP+q6b64\u001b\\\u001bP+q6b72\u001b\\\u001bP+q6b6c\u001b\\\u001bP+q2332\u001b\\\u001bP+q2334\u001b\\\u001bP+q2569\u001b\\\u001bP+q2a37\u001b\\\u001bP+q6b31\u001b\\\u001b[?25l\u001b[24;1H\u001b[1m\u001b[38;5;3m-- INSERT --\u001b[m\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m1   \u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[mT\u001b[6;32H\u001b[38;5;12m                 \u001b[8;33H               \u001b[9;29H                        \u001b[10;19H                                           \u001b[12;26H                             \u001b[13;18H                                              \u001b[15;18H                                              \u001b[16;18H                                              \u001b[17;18H                                              \u001b[m\u001b[23;11H\u001b[1m\u001b[7m\u001b[38;5;68m[+]\u001b[49C1,2 \u001b]2;[No Name] + - VIM\u0007\u001b]1;[No Name]\u0007\u001b[1;2H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bTh\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[1;3H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bhi\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[1;4H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bis\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[1;5H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;65H6 \u001b[1;6H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b i\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m7 \u001b[1;7H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bis\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m8 \u001b[1;8H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;65H9 \u001b[1;9H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b a\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m10\u001b[1;10H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H1 \u001b[1;11H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b t\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m2 \u001b[1;12H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bte\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[1;13H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bes\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[1;14H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bst\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[1;15H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bt.\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m6 \u001b[1;16H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[2;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m2,1  \u001b[2;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[3;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m3,\u001b[3;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[mH\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m2 \u001b[3;2H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bHo\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[3;3H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\boe\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[3;4H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;65H5 \u001b[3;5H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b f\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m6 \u001b[3;6H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bfu\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m7 \u001b[3;7H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[3;6H\u001b[K\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m6 \u001b[3;6H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[3;5H\u001b[K\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[3;5H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;65H4 \u001b[3;4H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[3;3H\u001b[K\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[3;3H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bop\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[3;4H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bpe\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[3;5H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bef\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m6 \u001b[3;6H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bfu\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m7 \u001b[3;7H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bul\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m8 \u001b[3;8H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bll\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m9 \u001b[3;9H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bly\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m10\u001b[3;10H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H1 \u001b[3;11H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b i\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m2 \u001b[3;12H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bit\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[3;13H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H4 \u001b[3;14H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b w\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[3;15H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bwi\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m6 \u001b[3;16H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bil\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m7 \u001b[3;17H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bll\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m8 \u001b[3;18H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H9 \u001b[3;19H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b w\u001b[23;65H\u001b[1m\u001b[7m\u001b[38;5;68m20 \u001b[3;20H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bwo\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m1 \u001b[3;21H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bor\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m2 \u001b[3;22H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\brk\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[3;23H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bk.\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[3;24H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[4;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m4,1  \u001b[4;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[5;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m5,\u001b[5;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[6;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m6,\u001b[6;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[7;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m7,\u001b[7;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[8;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m8,\u001b[8;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[9;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m9,\u001b[9;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[10;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m10,1\u001b[10;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[11;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m1,\u001b[11;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[12;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m2,\u001b[12;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[13;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m3,\u001b[13;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[14;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m4,\u001b[14;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[15;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m5,\u001b[15;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[16;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m6,\u001b[16;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[17;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m7,\u001b[17;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[18;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m8,\u001b[18;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[19;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m9,\u001b[19;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[20;1H\u001b[K\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m20,\u001b[20;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[21;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m1,\u001b[21;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[22;1H\u001b[K\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m2,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m3,\u001b[12CBot\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m4,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m5,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m6,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m7,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m8,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m9,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m30,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m1,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m2,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[22;1H\r\n\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m3,\u001b[22;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[mI\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m2 \u001b[22;2H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H3 \u001b[22;3H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b b\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[22;4H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bbe\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[22;5H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bet\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m6 \u001b[22;6H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H7 \u001b[22;7H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b i\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m8 \u001b[22;8H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bit\u001b[23;66H\u001b[1m\u001b[7m\u001b[38;5;68m9 \u001b[22;9H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[23;66H10\u001b[22;10H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\b w\u001b[23;67H\u001b[1m\u001b[7m\u001b[38;5;68m1 \u001b[22;11H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bwi\u001b[23;67H\u001b[1m\u001b[7m\u001b[38;5;68m2 \u001b[22;12H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bil\u001b[23;67H\u001b[1m\u001b[7m\u001b[38;5;68m3 \u001b[22;13H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bll\u001b[23;67H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[22;14H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\bl.\u001b[23;67H\u001b[1m\u001b[7m\u001b[38;5;68m5 \u001b[22;15H\u001b[?12l\u001b[?25h\u001b[m\u001b[24;1H\u001b[K\u001b[22;14H\u001b[?25l\u001b[23;67H\u001b[1m\u001b[7m\u001b[38;5;68m4 \u001b[22;14H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[11L\u001b[1;24r\u001b[1;1HThis is a test.\r\n\r\nHopefully it will work.\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m1,1   \u001b[9CTop\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[27m\u001b[m\u001b[H\u001b[2J\u001b[13;1HI bet it will.\r\n\u001b[38;5;12m~                                                                               \u001b[15;1H~                                                                               \u001b[16;1H~                                                                               \u001b[17;1H~                                                                               \u001b[18;1H~                                                                               \u001b[19;1H~                                                                               \u001b[20;1H~                                                                               \u001b[21;1H~                                                                               \u001b[22;1H~                                                                               \u001b[m\u001b[23;1H\u001b[1m\u001b[7m\u001b[38;5;68m[No Name] [+]                                                 21,0-1         Bot\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[L\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m0,\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[L\u001b[1;24r\u001b[23;63H\u001b[1m\u001b[7m\u001b[38;5;68m19,\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[L\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m8,\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[L\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m7,\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[L\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m6,\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[1;22r\u001b[m\u001b[1;1H\u001b[L\u001b[1;24r\u001b[23;64H\u001b[1m\u001b[7m\u001b[38;5;68m5,\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[24;1H\u001b[m:\u001b[?12l\u001b[?25hw\u001b[?25l\u001b[?12l\u001b[?25hq\u001b[?25l\u001b[?12l\u001b[?25h\r\u001b[?25l\u001b[38;5;15m\u001b[48;5;1mE32: No file name\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[24;1H\u001b[K\u001b[24;1H:\u001b[?12l\u001b[?25hw\u001b[?25l\u001b[?12l\u001b[?25h\u001b[?25l \u001b[?12l\u001b[?25hf\u001b[?25l\u001b[?12l\u001b[?25ho\u001b[?25l\u001b[?12l\u001b[?25ho\u001b[?25l\u001b[?12l\u001b[?25h\r\u001b[?25l\"foo\" [New] 33L, 85C written\u001b[23;1H\u001b[1m\u001b[7m\u001b[38;5;68mfoo           \u001b]2;foo (~/vt100-to-html/test) - VIM\u0007\u001b]1;foo\u0007\u001b[1;1H\u001b[?12l\u001b[?25h\u001b[?25l\u001b[m\u001b[24;1H\u001b[K\u001b[24;1H:\u001b[?12l\u001b[?25hq\u001b[?25l\u001b[?12l\u001b[?25h\r\u001b[?25l\u001b[?1002l\u001b]2;mark-desktop - vim\u0007\u001b]1;mark-desktop - vim\u0007\u001b[24;1H\u001b[K\u001b[24;1H\u001b[?1l\u001b>\u001b[?12l\u001b[?25h\u001b[?1049l\u001b]0;mark-desktop - ~/vt100-to-html/test\u0007\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m                                                                               \r\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[1;32m$\u001b[0m \u001b[K\u001b[57C\u001b[1;34m~/vt100-to-html/test\u001b[0m\u001b[77De\becho Yes!\r\r\n\u001b]0;mark-desktop - echo Yes!\u0007Yes!\r\n\u001b]0;mark-desktop - ~/vt100-to-html/test\u0007\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m                                                                               \r\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[1;32m$\u001b[0m \u001b[K\u001b[57C\u001b[1;34m~/vt100-to-html/test\u001b[0m\u001b[77D\r\r\n\nScript done on Sun 15 Aug 2010 11:54:14 PM EDT\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t0504-vim.text",
    "content": "t0005-CR.in               t0024-CUD.in           t0056-ED.in\nt0005-CR.text             t0024-CUD.text         t0056-ED.text\nt0006-IND.in              t0025-CUP.in           t0057-ED3.in\nt0006-IND.text            t0025-CUP.text         t0057-ED3.note\nt0007-space_at_end.in     t0026-CNL.in           t0057-ED3.text\nt0007-space_at_end.text   t0026-CNL.text         t0060-DECSC.in\nt0008-BS.in               t0027-CPL.in           t0060-DECSC.text\nt0008-BS.text             t0027-CPL.text         t0061-CSI_s.in\nt0009-NEL.in              t0030-HPR.in           t0061-CSI_s.text\nt0009-NEL.text            t0030-HPR.text         t008x-alt_screen_ED.in\nt0010-RI.in               t0031-HPB.in           t008x-IRM.in\nt0010-RI.text             t0031-HPB.text         t008x-NLM.in\nt0011-RI_scroll.in        t0032-VPB.in           t008x-save_cursor_mode.in\nt0011-RI_scroll.text      t0032-VPB.text         t0500-bash_long_line.in\nt0012-VT.in               t0033-VPB_scroll.in    t0500-bash_long_line.text\nt0012-VT.text             t0033-VPB_scroll.text  t0501-bash_ls.in\nt0013-FF.in               t0034-VPR.in           t0501-bash_ls.text\nt0013-FF.text             t0034-VPR.text         t0502-bash_ls_color.in\nt0014-CAN.in              t0035-HVP.in           t0502-bash_ls_color.text\nt0014-CAN.text            t0035-HVP.text         t0503-zsh_ls_color.in\nt0015-SUB.in              t0040-REP.in           t0503-zsh_ls_color.text\nt0015-SUB.text            t0040-REP.text         typescript\nt0016-SU.in               t0050-ICH.in\n$ vim                                                      ~/vt100-to-html/test\nScript done on Sun 15 Aug 2010 11:54:14 PM EDT             ~/vt100-to-html/test\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t600-DECSTBM_SR.in",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\u001b[10;15r\u001b[5;5H\u001b[5 A\u001b[12;12H\u001b[5 A\u001b[20;20H\u001b[5 A"
  },
  {
    "path": "fixtures/escape_sequence_files/t600-DECSTBM_SR.text",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\n     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\n     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\n     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\n     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\n     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\n     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t601-DECSTBM_SL.in",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\u001b[10;15r\u001b[5;5H\u001b[5 @\u001b[12;12H\u001b[5 @\u001b[20;20H\u001b[5 @"
  },
  {
    "path": "fixtures/escape_sequence_files/t601-DECSTBM_SL.text",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t602-DECSTBM_DECIC.in",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\u001b[10;15r\u001b[5;5H\u001b[5'}\u001b[12;12H\u001b[5'}\u001b[20;20H\u001b[5'}"
  },
  {
    "path": "fixtures/escape_sequence_files/t602-DECSTBM_DECIC.text",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijk     lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijk     lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijk     lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijk     lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijk     lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijk     lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\n"
  },
  {
    "path": "fixtures/escape_sequence_files/t603-DECSTBM_DECDC.in",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\u001b[10;15r\u001b[5;5H\u001b[5'~\u001b[12;12H\u001b[5'~\u001b[20;20H\u001b[5'~"
  },
  {
    "path": "fixtures/escape_sequence_files/t603-DECSTBM_DECDC.text",
    "content": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB\n"
  },
  {
    "path": "headless/.gitignore",
    "content": "lib-headless/\ntypings/\nlogo-full.png\n./package.json\n"
  },
  {
    "path": "headless/.npmignore",
    "content": "# Blacklist - exclude everything except npm defaults such as LICENSE, etc\n*\n!*/\n\n# Whitelist - lib-headless/\n!lib-headless/**/*.d.ts\n\n!lib-headless/**/*.js\n!lib-headless/**/*.js.map\n\n!lib-headless/**/*.mjs\n!lib-headless/**/*.mjs.map\n\n!lib-headless/**/*.css\n\n# Whitelist - src/\n!src/**/*.ts\n!src/**/*.d.ts\n\n!src/**/*.js\n!src/**/*.js.map\n\n!src/**/*.css\n\n# Blacklist - src/ test files\nsrc/**/*.test.ts\nsrc/**/*.test.d.ts\nsrc/**/*.test.js\nsrc/**/*.test.js.map\n\n# Whitelist - typings/\n!typings/*.d.ts\n"
  },
  {
    "path": "headless/README.md",
    "content": "# [![xterm.js logo](logo-full.png)](https://xtermjs.org)\n\n⚠ This package is experimental\n\n`@xterm/headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/@xterm/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted.\n\n## Getting Started\n\nFirst, you need to install the module, we ship exclusively through npm, so you need that installed and then add xterm.js as a dependency by running:\n\n```sh\nnpm install @xterm/headless\n```\n\nThen import as you would a regular node package. The recommended way to load `@xterm/headless` is with TypeScript and the ES6 module syntax:\n\n```javascript\nimport { Terminal } from '@xterm/headless';\n```\n\n## API\n\nThe full API for `@xterm/headless` is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm-headless.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.\n\nNote 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.\n\n### Addons\n\nAddons in `@xterm/headless` work the [same as in `xterm`](https://github.com/xtermjs/xterm.js/blob/master/README.md#addons) with the one caveat being that the addon needs to be packaged for node.js and not use any DOM APIs.\n\nCurrently no official addons are packaged on npm.\n"
  },
  {
    "path": "headless/package.json",
    "content": "{\n \"name\": \"@xterm/headless\",\n \"description\": \"A headless terminal component that runs in Node.js\",\n \"version\": \"6.0.0\",\n \"main\": \"lib-headless/xterm-headless.js\",\n \"module\": \"lib-headless/xterm-headless.mjs\",\n \"types\": \"typings/xterm-headless.d.ts\",\n \"exports\": {\n  \"types\": \"./typings/xterm-headless.d.ts\",\n  \"import\": \"./lib-headless/xterm-headless.mjs\",\n  \"require\": \"./lib-headless/xterm-headless.js\"\n },\n \"repository\": \"https://github.com/xtermjs/xterm.js\",\n \"license\": \"MIT\",\n \"keywords\": [\n  \"cli\",\n  \"command-line\",\n  \"console\",\n  \"pty\",\n  \"shell\",\n  \"ssh\",\n  \"styles\",\n  \"terminal-emulator\",\n  \"terminal\",\n  \"tty\",\n  \"vt100\",\n  \"webgl\",\n  \"xterm\"\n ]\n}\n"
  },
  {
    "path": "images/build-flow.tldr",
    "content": "{\n\t\"tldrawFileFormatVersion\": 1,\n\t\"schema\": {\n\t\t\"schemaVersion\": 2,\n\t\t\"sequences\": {\n\t\t\t\"com.tldraw.store\": 4,\n\t\t\t\"com.tldraw.asset\": 1,\n\t\t\t\"com.tldraw.camera\": 1,\n\t\t\t\"com.tldraw.document\": 2,\n\t\t\t\"com.tldraw.instance\": 25,\n\t\t\t\"com.tldraw.instance_page_state\": 5,\n\t\t\t\"com.tldraw.page\": 1,\n\t\t\t\"com.tldraw.instance_presence\": 5,\n\t\t\t\"com.tldraw.pointer\": 1,\n\t\t\t\"com.tldraw.shape\": 4,\n\t\t\t\"com.tldraw.asset.bookmark\": 2,\n\t\t\t\"com.tldraw.asset.image\": 5,\n\t\t\t\"com.tldraw.asset.video\": 5,\n\t\t\t\"com.tldraw.shape.group\": 0,\n\t\t\t\"com.tldraw.shape.text\": 2,\n\t\t\t\"com.tldraw.shape.bookmark\": 2,\n\t\t\t\"com.tldraw.shape.draw\": 2,\n\t\t\t\"com.tldraw.shape.geo\": 9,\n\t\t\t\"com.tldraw.shape.note\": 7,\n\t\t\t\"com.tldraw.shape.line\": 5,\n\t\t\t\"com.tldraw.shape.frame\": 0,\n\t\t\t\"com.tldraw.shape.arrow\": 5,\n\t\t\t\"com.tldraw.shape.highlight\": 1,\n\t\t\t\"com.tldraw.shape.embed\": 4,\n\t\t\t\"com.tldraw.shape.image\": 4,\n\t\t\t\"com.tldraw.shape.video\": 2,\n\t\t\t\"com.tldraw.binding.arrow\": 0\n\t\t}\n\t},\n\t\"records\": [\n\t\t{\n\t\t\t\"gridSize\": 10,\n\t\t\t\"name\": \"\",\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"document:document\",\n\t\t\t\"typeName\": \"document\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Page 1\",\n\t\t\t\"index\": \"a1\",\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"typeName\": \"page\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 636.4313349947166,\n\t\t\t\"y\": 1700.044465460126,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:3iBwqdyYNjwbQfSubqnom\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 213.3333740234375,\n\t\t\t\t\"text\": \"Webpack\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0I\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 2092.797230887739,\n\t\t\t\"y\": 2146.9973594336225,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:5qA9o2cFXyVPGy-Y7wmHq\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 385.0825933601718,\n\t\t\t\t\"text\": \"Webpack builds UMD packages\\n-> lib/*.js(.map)?\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"az\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 2090.650868458008,\n\t\t\t\"y\": 2243.5894987860306,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:AXaJVAY5z7hx4-ARTlQ6-\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 382.22064577563515,\n\t\t\t\t\"text\": \"Esbuild builds the ESM module\\n-> lib/*.mjs(.map)?\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b00\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 596.0979609712791,\n\t\t\t\"y\": 1656.8570474761173,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:AY2I3PZsnwtQIGUw_Mtah\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 26.66668701171875,\n\t\t\t\t\"h\": 28.6666259765625,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-blue\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0F\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1380.8769935332364,\n\t\t\t\"y\": 2242.5163830769466,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:AgR_Hcb4ELZjOVxZFH3Wv\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 304.9468818890839,\n\t\t\t\t\"text\": \"Builds prod bundles for core and addons, _replacing_ the development bundles\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"ay\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 596.0979609712791,\n\t\t\t\"y\": 1618.1904214995548,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:BRI40dtnKD15_eUIWwwQi\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 26.66668701171875,\n\t\t\t\t\"h\": 28.6666259765625,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-green\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0J\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 636.0979609712791,\n\t\t\t\"y\": 1621.3778394835635,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:CH5pcQB_3kMsgs7TXvKV_\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 213.3333740234375,\n\t\t\t\t\"text\": \"tsc\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0K\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1377.7305783188865,\n\t\t\t\"y\": 1693.6887909112806,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:J77TBIP0y5VuAhRj0L_LT\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 302.8002574362281,\n\t\t\t\t\"text\": \"Builds .ts files via tsc primarily to verify types. This also outputs to out/ as it's required for project references to work\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"ap\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 987.3333129882812,\n\t\t\t\"y\": 1937.1057175467179,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:T4DZ2ud7lktKNMr2BzsJl\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 252,\n\t\t\t\t\"text\": \"Installs addon dependencies and does an initial build which creates the .d.ts files required for composite projects to work.\\n\\nThis is split out from the main npm ci task to optimize CI performance\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"av\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 601.9999389648438,\n\t\t\t\"y\": 1933.7723435232804,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:Wry6txBWNuvwfovZUhnol\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 252,\n\t\t\t\t\"text\": \"Installs dependencies\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"middle\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"au\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 596.4313349947166,\n\t\t\t\"y\": 1696.8570474761173,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:ZB_9qrwefpDJnfZr9qBm_\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 26.66668701171875,\n\t\t\t\t\"h\": 28.6666259765625,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-red\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0H\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1751.9738975103173,\n\t\t\t\"y\": 1938.0236502787513,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:_SsmWyixs8ObT3AlRxJEC\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 328,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-blue\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run esbuild-demo-watch\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 12.17270834470628,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b07\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 636.0979609712791,\n\t\t\t\"y\": 1660.044465460126,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:uyHYCSjGJIXii4e23dT7B\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 213.3333740234375,\n\t\t\t\t\"text\": \"Esbuild\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0G\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1375.80374681259,\n\t\t\t\"y\": 1933.0158737055683,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:xmFNakrhYqROS4fwWVBPw\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 337.8597376517239,\n\t\t\t\t\"text\": \"Builds:\\n- Dev bundles for core and addons\\n  -> lib/\\n- Unit test output\\n  -> out-esbuild/\\n- Integration test output\\n  -> out-esbuild-test/\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"at\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 2093.5129470541074,\n\t\t\t\"y\": 2054.698075952239,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:2GWdfZzAdTwK0kGXw0UbA\",\n\t\t\t\"type\": \"text\",\n\t\t\t\"props\": {\n\t\t\t\t\"color\": \"grey\",\n\t\t\t\t\"size\": \"s\",\n\t\t\t\t\"w\": 389.37558024275813,\n\t\t\t\t\"text\": \"This produced the same output as yarn watch, it just verifies it's valid and up to date\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"textAlign\": \"start\",\n\t\t\t\t\"autoSize\": false,\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0Q\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1751.9738975103173,\n\t\t\t\"y\": 1843.577938861855,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:_-OyqDGUPp6bT5RQXdwMI\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run  test-unit\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1751.9738975103173,\n\t\t\t\"y\": 1749.1322274449587,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:ifF--80trdjZJw_a9C6_3\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run test-integration\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08V\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1364.174169791073,\n\t\t\t\"y\": 1843.577938861855,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:WVC9r4PNXcS7eZPfhTFgY\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-blue\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run esbuild-watch\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08G\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 977.0898307092907,\n\t\t\t\"y\": 1843.577938861855,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:v-NBiB5tnYj6mfPcs2Lnx\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run setup\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08O\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 593.5829588610696,\n\t\t\t\"y\": 1843.577938861855,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:8ZPtlRai9BtQEJ2gmIV3c\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm ci\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08S\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 772.1903969012261,\n\t\t\t\"y\": 1885.4237569305146,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:V9XyLiNX1PjiMwRwIiRg7\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 0,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 172.45871189273217,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08U\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:0OeZ_I-yeh5gupuSA2zEt\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:V9XyLiNX1PjiMwRwIiRg7\",\n\t\t\t\"toId\": \"shape:8ZPtlRai9BtQEJ2gmIV3c\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.5448101281512607,\n\t\t\t\t\t\"y\": 0.5281326215444239\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:sJPmdLqk_mhvW3EEbhn8B\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:V9XyLiNX1PjiMwRwIiRg7\",\n\t\t\t\"toId\": \"shape:v-NBiB5tnYj6mfPcs2Lnx\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.6300811819455899,\n\t\t\t\t\t\"y\": 0.5281326215444239\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1139.95626225848,\n\t\t\t\"y\": 1883.2772634892217,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:aF2tu25vqOMqxqZdm2T7W\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 0,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 208.4175260519379,\n\t\t\t\t\t\"y\": 1.7763568394002505e-15\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08Q\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:3vKuqcZTdkxV2DlktP6TP\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:aF2tu25vqOMqxqZdm2T7W\",\n\t\t\t\"toId\": \"shape:v-NBiB5tnYj6mfPcs2Lnx\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.496794995871912,\n\t\t\t\t\t\"y\": 0.5010419047034265\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1537.0575488977029,\n\t\t\t\"y\": 1887.5702503718078,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:60sCzaecqva45lkSiZAVl\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 0,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 214.66007632821805,\n\t\t\t\t\t\"y\": -4.440892098500626e-16\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08H\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:ezDRjOuSTPVueGBDR7nqP\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:60sCzaecqva45lkSiZAVl\",\n\t\t\t\"toId\": \"shape:_-OyqDGUPp6bT5RQXdwMI\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.3724298543122986,\n\t\t\t\t\t\"y\": 0.5552233383854241\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1544.9280685196318,\n\t\t\t\"y\": 1882.5617438401969,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:qZILDsrMvHBzlQNc_O-C_\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 0,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 213.93388998606633,\n\t\t\t\t\t\"y\": -47.22285570844815\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08d77a\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:0Mdiw6PKULo_x871SedFg\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:qZILDsrMvHBzlQNc_O-C_\",\n\t\t\t\"toId\": \"shape:ifF--80trdjZJw_a9C6_3\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.652351030278442,\n\t\t\t\t\t\"y\": 0.26058821052445413\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1534.91105545641,\n\t\t\t\"y\": 1887.5702503718078,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:cNDESXGWiAfDVDBDVfg7b\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 0,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 208.94752849735062,\n\t\t\t\t\t\"y\": 55.98732152134402\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08GV\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:HE_zH5KnOpej1I1rPGCIg\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:cNDESXGWiAfDVDBDVfg7b\",\n\t\t\t\"toId\": \"shape:_SsmWyixs8ObT3AlRxJEC\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.450606605772231,\n\t\t\t\t\t\"y\": 0.5968560480685863\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 2127.610249736609,\n\t\t\t\"y\": 1938.0236502787513,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:JMPdmTnw2SBt9X4ofLook\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 328,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run  run demo-server\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 12.17270834470628,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b07V\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1999.9846780737685,\n\t\t\t\"y\": 1979.153948698386,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:WB5zaxgM1wNkdlwFp_L7b\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 0,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 116.63487849234014,\n\t\t\t\t\t\"y\": 0\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b07l\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:ICkilWs-yx4TPeZZqkaDL\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:WB5zaxgM1wNkdlwFp_L7b\",\n\t\t\t\"toId\": \"shape:JMPdmTnw2SBt9X4ofLook\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.309223350646172,\n\t\t\t\t\t\"y\": 0.5191021070164011\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1364.174169791073,\n\t\t\t\"y\": 1608.8946341118826,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:-wezbN2Ivtn3HYMybEMH-\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-green\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run tsc-watch\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08K\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1132.8013277913572,\n\t\t\t\"y\": 1883.9927176324652,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:HDZ8-LJElh_9IZAjkulfU\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 172.7701855355947,\n\t\t\t\t\t\"y\": -39.434889886239034\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 230.39025236160478,\n\t\t\t\t\t\"y\": -203.91687692284427\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08P\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:LZttCfpr-Xi05xUCSDmEH\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:HDZ8-LJElh_9IZAjkulfU\",\n\t\t\t\"toId\": \"shape:-wezbN2Ivtn3HYMybEMH-\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.04065328730853116,\n\t\t\t\t\t\"y\": 0.636495488908422\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:bD7XAJBcYa-YkWP8gMVvn\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:HDZ8-LJElh_9IZAjkulfU\",\n\t\t\t\"toId\": \"shape:v-NBiB5tnYj6mfPcs2Lnx\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.9779671592353165,\n\t\t\t\t\t\"y\": 0.17491139790800858\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:Yq-JfMplao9i2Ev9FrITO\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:aF2tu25vqOMqxqZdm2T7W\",\n\t\t\t\"toId\": \"shape:WVC9r4PNXcS7eZPfhTFgY\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.4359423362795411,\n\t\t\t\t\t\"y\": 0.5010419047034265\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1364.174169791073,\n\t\t\t\"y\": 2149.0954616687186,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:QB0hllOTM21eeI5w8vMKe\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run package\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08I\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1298.7967769141692,\n\t\t\t\"y\": 1921.1986257834726,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:hFXCnINjXmaE3oA3bU0EP\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 6.774736412782886,\n\t\t\t\t\t\"y\": 0.6329658493050374\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 65.1103883935989,\n\t\t\t\t\t\"y\": 248.2777850467553\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08OV\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:yMfdlYSjRO6GptScoPy1c\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:hFXCnINjXmaE3oA3bU0EP\",\n\t\t\t\"toId\": \"shape:v-NBiB5tnYj6mfPcs2Lnx\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.984514654567308,\n\t\t\t\t\t\"y\": 0.9244209549279696\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:sferkEx0DKOBcZ5JleV6w\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:hFXCnINjXmaE3oA3bU0EP\",\n\t\t\t\"toId\": \"shape:QB0hllOTM21eeI5w8vMKe\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.02319276692002185,\n\t\t\t\t\t\"y\": 0.2481957653494725\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:ttVkss_5JcENWt7HN9vWI\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:WB5zaxgM1wNkdlwFp_L7b\",\n\t\t\t\"toId\": \"shape:_SsmWyixs8ObT3AlRxJEC\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.7565126437303756,\n\t\t\t\t\t\"y\": 0.5191021070164011\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:HswFX0DYxOpwfM3ROmmEj\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:60sCzaecqva45lkSiZAVl\",\n\t\t\t\"toId\": \"shape:WVC9r4PNXcS7eZPfhTFgY\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.5273499074833017,\n\t\t\t\t\t\"y\": 0.5552233383854241\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:q7-RWAM78r2Sidz_m4Dze\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:cNDESXGWiAfDVDBDVfg7b\",\n\t\t\t\"toId\": \"shape:WVC9r4PNXcS7eZPfhTFgY\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.5208024121513116,\n\t\t\t\t\t\"y\": 0.5552233383854241\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:fAT5gNXJGQYPVLjLP501Y\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:qZILDsrMvHBzlQNc_O-C_\",\n\t\t\t\"toId\": \"shape:WVC9r4PNXcS7eZPfhTFgY\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.5513575235764011,\n\t\t\t\t\t\"y\": 0.49201139017540374\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1544.9280685196318,\n\t\t\t\"y\": 2188.0793321528417,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:eaWLv1GGYWGHYA41F7K6N\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 145.83206845090626,\n\t\t\t\t\t\"y\": -38.98380497834182\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 213.93388998606633,\n\t\t\t\t\t\"y\": -47.22285570844815\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b0A\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1751.9738975103173,\n\t\t\t\"y\": 2054.6498157576034,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:Du569XMPi-JFT0tNFfeTM\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-green\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run build\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08l\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1534.91105545641,\n\t\t\t\"y\": 2193.0878386844524,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:_sQStpmh5TptLm-WzSdcD\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 155.8490815141281,\n\t\t\t\t\t\"y\": 35.24123014534098\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 208.94752849735062,\n\t\t\t\t\t\"y\": 55.98732152134402\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08J\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1537.0575488977029,\n\t\t\t\"y\": 2193.0878386844524,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:0ANJP498zbKf7pW-asL1i\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"props\": {\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"color\": \"black\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"bend\": 0,\n\t\t\t\t\"start\": {\n\t\t\t\t\t\"x\": 154.95090445604274,\n\t\t\t\t\t\"y\": -2.302500165913898\n\t\t\t\t},\n\t\t\t\t\"end\": {\n\t\t\t\t\t\"x\": 214.66007632821805,\n\t\t\t\t\t\"y\": -4.440892098500626e-16\n\t\t\t\t},\n\t\t\t\t\"arrowheadStart\": \"none\",\n\t\t\t\t\"arrowheadEnd\": \"arrow\",\n\t\t\t\t\"text\": \"\",\n\t\t\t\t\"labelPosition\": 0.5,\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b08IV\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1751.9738975103173,\n\t\t\t\"y\": 2149.0955271744997,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:K9oCSoENclInv9a_tXabj\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-red\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"webpack\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b084\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"x\": 1751.9738975103173,\n\t\t\t\"y\": 2243.541238591396,\n\t\t\t\"rotation\": 0,\n\t\t\t\"isLocked\": false,\n\t\t\t\"opacity\": 1,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"shape:KlL_Upgwj-3XgTpiNEhk2\",\n\t\t\t\"type\": \"geo\",\n\t\t\t\"props\": {\n\t\t\t\t\"w\": 327.8342835626727,\n\t\t\t\t\"h\": 79.23354165529372,\n\t\t\t\t\"geo\": \"rectangle\",\n\t\t\t\t\"color\": \"light-blue\",\n\t\t\t\t\"labelColor\": \"black\",\n\t\t\t\t\"fill\": \"none\",\n\t\t\t\t\"dash\": \"draw\",\n\t\t\t\t\"size\": \"m\",\n\t\t\t\t\"font\": \"draw\",\n\t\t\t\t\"text\": \"npm run esbuild-package\",\n\t\t\t\t\"align\": \"middle\",\n\t\t\t\t\"verticalAlign\": \"middle\",\n\t\t\t\t\"growY\": 0,\n\t\t\t\t\"url\": \"\",\n\t\t\t\t\"scale\": 1\n\t\t\t},\n\t\t\t\"parentId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"index\": \"b078\",\n\t\t\t\"typeName\": \"shape\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:l-OabcSkuuqXMbvMlhbun\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:_sQStpmh5TptLm-WzSdcD\",\n\t\t\t\"toId\": \"shape:KlL_Upgwj-3XgTpiNEhk2\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.450606605772231,\n\t\t\t\t\t\"y\": 0.5968560480685863\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:SCQfe4J3hk0pxwwX2dFxg\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:0ANJP498zbKf7pW-asL1i\",\n\t\t\t\"toId\": \"shape:K9oCSoENclInv9a_tXabj\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.3724298543122986,\n\t\t\t\t\t\"y\": 0.5552233383854241\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:UFsW9FfDEsajX0nfIQnns\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:eaWLv1GGYWGHYA41F7K6N\",\n\t\t\t\"toId\": \"shape:Du569XMPi-JFT0tNFfeTM\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": false,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.652351030278442,\n\t\t\t\t\t\"y\": 0.26058821052445413\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"end\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:NQ4FN6oXkUFqj5XAV3t5I\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:eaWLv1GGYWGHYA41F7K6N\",\n\t\t\t\"toId\": \"shape:QB0hllOTM21eeI5w8vMKe\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.985280007757135,\n\t\t\t\t\t\"y\": 0.04515174589704865\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:jGa3U4qEd8JSLH18yKM1u\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:_sQStpmh5TptLm-WzSdcD\",\n\t\t\t\"toId\": \"shape:QB0hllOTM21eeI5w8vMKe\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.9569072615669089,\n\t\t\t\t\t\"y\": 0.9458193930610704\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"binding:x1oHIbne8_3daO1FYSS7l\",\n\t\t\t\"type\": \"arrow\",\n\t\t\t\"fromId\": \"shape:0ANJP498zbKf7pW-asL1i\",\n\t\t\t\"toId\": \"shape:QB0hllOTM21eeI5w8vMKe\",\n\t\t\t\"props\": {\n\t\t\t\t\"isPrecise\": true,\n\t\t\t\t\"isExact\": false,\n\t\t\t\t\"normalizedAnchor\": {\n\t\t\t\t\t\"x\": 0.36485735791959295,\n\t\t\t\t\t\"y\": 0.5552241651284893\n\t\t\t\t},\n\t\t\t\t\"terminal\": \"start\"\n\t\t\t},\n\t\t\t\"typeName\": \"binding\"\n\t\t},\n\t\t{\n\t\t\t\"id\": \"pointer:pointer\",\n\t\t\t\"typeName\": \"pointer\",\n\t\t\t\"x\": 1400.3302271347488,\n\t\t\t\"y\": 2047.8884891660964,\n\t\t\t\"lastActivityTimestamp\": 1763917251678,\n\t\t\t\"meta\": {}\n\t\t},\n\t\t{\n\t\t\t\"followingUserId\": null,\n\t\t\t\"opacityForNextShape\": 1,\n\t\t\t\"stylesForNextShape\": {},\n\t\t\t\"brush\": null,\n\t\t\t\"scribbles\": [],\n\t\t\t\"cursor\": {\n\t\t\t\t\"type\": \"default\",\n\t\t\t\t\"rotation\": 0\n\t\t\t},\n\t\t\t\"isFocusMode\": false,\n\t\t\t\"exportBackground\": true,\n\t\t\t\"isDebugMode\": false,\n\t\t\t\"isToolLocked\": false,\n\t\t\t\"screenBounds\": {\n\t\t\t\t\"x\": 0,\n\t\t\t\t\"y\": 0,\n\t\t\t\t\"w\": 1498,\n\t\t\t\t\"h\": 859\n\t\t\t},\n\t\t\t\"insets\": [\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\ttrue,\n\t\t\t\tfalse\n\t\t\t],\n\t\t\t\"zoomBrush\": null,\n\t\t\t\"isGridMode\": false,\n\t\t\t\"isPenMode\": false,\n\t\t\t\"chatMessage\": \"\",\n\t\t\t\"isChatting\": false,\n\t\t\t\"highlightedUserIds\": [],\n\t\t\t\"isFocused\": true,\n\t\t\t\"devicePixelRatio\": 1,\n\t\t\t\"isCoarsePointer\": false,\n\t\t\t\"isHoveringCanvas\": true,\n\t\t\t\"openMenus\": [],\n\t\t\t\"isChangingStyle\": false,\n\t\t\t\"isReadonly\": false,\n\t\t\t\"meta\": {},\n\t\t\t\"duplicateProps\": null,\n\t\t\t\"id\": \"instance:instance\",\n\t\t\t\"currentPageId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"typeName\": \"instance\"\n\t\t},\n\t\t{\n\t\t\t\"editingShapeId\": \"shape:T4DZ2ud7lktKNMr2BzsJl\",\n\t\t\t\"croppingShapeId\": null,\n\t\t\t\"selectedShapeIds\": [\n\t\t\t\t\"shape:T4DZ2ud7lktKNMr2BzsJl\"\n\t\t\t],\n\t\t\t\"hoveredShapeId\": \"shape:xmFNakrhYqROS4fwWVBPw\",\n\t\t\t\"erasingShapeIds\": [],\n\t\t\t\"hintingShapeIds\": [],\n\t\t\t\"focusedGroupId\": null,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"instance_page_state:page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"pageId\": \"page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"typeName\": \"instance_page_state\"\n\t\t},\n\t\t{\n\t\t\t\"x\": -505.3234286567697,\n\t\t\t\"y\": -1373.530516198867,\n\t\t\t\"z\": 0.7251341566384403,\n\t\t\t\"meta\": {},\n\t\t\t\"id\": \"camera:page:Kiitk22EwODN5nXZh481X\",\n\t\t\t\"typeName\": \"camera\"\n\t\t}\n\t]\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@xterm/xterm\",\n  \"description\": \"Full xterm terminal, in your browser\",\n  \"version\": \"6.0.0\",\n  \"main\": \"lib/xterm.js\",\n  \"module\": \"lib/xterm.mjs\",\n  \"style\": \"css/xterm.css\",\n  \"types\": \"typings/xterm.d.ts\",\n  \"repository\": \"https://github.com/xtermjs/xterm.js\",\n  \"license\": \"MIT\",\n  \"workspaces\": [\n    \"addons/*\"\n  ],\n  \"keywords\": [\n    \"cli\",\n    \"command-line\",\n    \"console\",\n    \"pty\",\n    \"shell\",\n    \"ssh\",\n    \"styles\",\n    \"terminal-emulator\",\n    \"terminal\",\n    \"tty\",\n    \"vt100\",\n    \"webgl\",\n    \"xterm\"\n  ],\n  \"scripts\": {\n    \"presetup\": \"npm run build\",\n    \"setup\": \"npm run esbuild\",\n    \"postsetup\": \"npm run esbuild-demo-server\",\n    \"start\": \"node demo/start\",\n    \"dev\": \"concurrently -k -p [{name}] -n tsc,esbuild,esbuild-demo-client,esbuild-demo-server,server -c blue,yellow,cyan,green,magenta \\\"npm:tsc-watch\\\" \\\"npm:esbuild-watch\\\" \\\"npm:esbuild-demo-client-watch\\\" \\\"npm:esbuild-demo-server-watch\\\" \\\"npm:start\\\"\",\n    \"build\": \"npm run tsc\",\n    \"watch\": \"npm run tsc-watch\",\n    \"tsc\": \"tsgo -b ./tsconfig.all.json\",\n    \"tsc-watch\": \"tsgo -b -w ./tsconfig.all.json --preserveWatchOutput\",\n    \"esbuild\": \"node bin/esbuild_all.mjs\",\n    \"esbuild-watch\": \"node bin/esbuild_all.mjs --watch\",\n    \"esbuild-package\": \"node bin/esbuild_all.mjs --prod\",\n    \"esbuild-package-watch\": \"node bin/esbuild_all.mjs --prod --watch\",\n    \"esbuild-package-headless-only\": \"node bin/esbuild.mjs --prod --headless\",\n    \"esbuild-demo-client\": \"node bin/esbuild.mjs --demo-client\",\n    \"esbuild-demo-client-watch\": \"node bin/esbuild.mjs --demo-client --watch\",\n    \"esbuild-demo-server\": \"node bin/esbuild.mjs --demo-server\",\n    \"esbuild-demo-server-watch\": \"node bin/esbuild.mjs --demo-server --watch\",\n    \"test\": \"npm run test-unit\",\n    \"posttest\": \"npm run lint\",\n    \"lint\": \"eslint --max-warnings 0 src/ addons/ demo/ test/\",\n    \"lint-changes\": \"node ./bin/lint_changes.js\",\n    \"lint-changes-fix\": \"node ./bin/lint_changes.js --fix\",\n    \"lint-fix\": \"eslint --fix src/ addons/ demo/ test/\",\n    \"lint-api\": \"eslint --config eslint.config.typings.mjs --max-warnings 0 typings/\",\n    \"test-unit\": \"node ./bin/test_unit.js\",\n    \"test-unit-slow-tests\": \"npm run test-unit | grep \\\"ms)\\\"\",\n    \"test-unit-coverage\": \"node ./bin/test_unit.js --coverage\",\n    \"test-unit-dev\": \"cross-env NODE_PATH='./out' mocha\",\n    \"test-integration\": \"node ./bin/test_integration.js --workers=75%\",\n    \"test-integration-chromium\": \"node ./bin/test_integration.js --workers=75% \\\"--project=Chromium\\\"\",\n    \"test-integration-firefox\": \"node ./bin/test_integration.js --workers=75% \\\"--project=FirefoxStable\\\"\",\n    \"test-integration-webkit\": \"node ./bin/test_integration.js --workers=75% \\\"--project=WebKit\\\"\",\n    \"test-integration-debug\": \"node ./bin/test_integration.js  --workers=1 --headed --timeout=30000\",\n    \"benchmark\": \"NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json\",\n    \"benchmark-baseline\": \"NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/*benchmark.js\",\n    \"benchmark-eval\": \"NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/*benchmark.js\",\n    \"clean\": \"rm -rf lib out addons/*/lib addons/*/out\",\n    \"vtfeatures\": \"node bin/extract_vtfeatures.js src/**/*.ts src/*.ts\",\n    \"prepackage\": \"npm run build\",\n    \"package\": \"webpack\",\n    \"postpackage\": \"npm run esbuild-package\",\n    \"prepackage-headless\": \"npm run esbuild-package-headless-only\",\n    \"package-headless\": \"webpack --config ./webpack.config.headless.js\",\n    \"postpackage-headless\": \"node ./bin/package_headless.js\",\n    \"prepublishOnly\": \"npm run package\",\n    \"agent:setup-repo\": \"node bin/agent/setup-repo.mjs\"\n  },\n  \"devDependencies\": {\n    \"@lunapaint/png-codec\": \"^0.2.0\",\n    \"@playwright/test\": \"^1.57.0\",\n    \"@stylistic/eslint-plugin\": \"^4.4.1\",\n    \"@types/chai\": \"^4.2.22\",\n    \"@types/debug\": \"^4.1.7\",\n    \"@types/deep-equal\": \"^1.0.1\",\n    \"@types/express\": \"4\",\n    \"@types/express-ws\": \"^3.0.1\",\n    \"@types/jsdom\": \"^27.0.0\",\n    \"@types/mocha\": \"^9.0.0\",\n    \"@types/node\": \"^22.19.3\",\n    \"@types/trusted-types\": \"^1.0.6\",\n    \"@types/utf8\": \"^3.0.0\",\n    \"@types/webpack\": \"^5.28.0\",\n    \"@types/ws\": \"^8.2.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.50.1\",\n    \"@typescript-eslint/parser\": \"^8.50.1\",\n    \"@typescript/native-preview\": \"^7.0.0-dev.20260213.1\",\n    \"chai\": \"^4.3.4\",\n    \"concurrently\": \"^9.1.2\",\n    \"cross-env\": \"^7.0.3\",\n    \"deep-equal\": \"^2.0.5\",\n    \"esbuild\": \"~0.25.2\",\n    \"eslint\": \"^9.39.2\",\n    \"eslint-plugin-jsdoc\": \"^50.8.0\",\n    \"express\": \"^4.19.2\",\n    \"express-ws\": \"^5.0.2\",\n    \"jsdom\": \"^27.3.0\",\n    \"mocha\": \"^10.1.0\",\n    \"mustache\": \"^4.2.0\",\n    \"node-pty\": \"^1.2.0-beta.9\",\n    \"nyc\": \"^17.1.0\",\n    \"source-map-loader\": \"^3.0.0\",\n    \"source-map-support\": \"^0.5.20\",\n    \"ts-loader\": \"^9.3.1\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.50.1\",\n    \"utf8\": \"^3.0.0\",\n    \"webpack\": \"^5.61.0\",\n    \"webpack-cli\": \"^4.9.1\",\n    \"ws\": \"^8.2.3\",\n    \"xterm-benchmark\": \"^0.3.1\"\n  }\n}\n"
  },
  {
    "path": "src/browser/AccessibilityManager.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as Strings from 'browser/LocalizableStrings';\nimport { ITerminal, IRenderDebouncer } from 'browser/Types';\nimport { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { ICoreBrowserService, IRenderService } from 'browser/services/Services';\nimport { IBuffer } from 'common/buffer/Types';\nimport { IInstantiationService } from 'common/services/Services';\nimport { addDisposableListener } from 'browser/Dom';\n\nconst MAX_ROWS_TO_READ = 20;\n\nconst enum BoundaryPosition {\n  TOP,\n  BOTTOM\n}\n\n// Turn this on to unhide the accessibility tree and display it under\n// (instead of overlapping with) the terminal.\nconst DEBUG = false;\n\nexport class AccessibilityManager extends Disposable {\n  private _debugRootContainer: HTMLElement | undefined;\n  private _accessibilityContainer: HTMLElement;\n\n  private _rowContainer: HTMLElement;\n  private _rowElements: HTMLElement[];\n  private _rowColumns: WeakMap<HTMLElement, number[]> = new WeakMap();\n\n  private _liveRegion: HTMLElement;\n  private _liveRegionLineCount: number = 0;\n  private _liveRegionDebouncer: IRenderDebouncer;\n\n  private _topBoundaryFocusListener: (e: FocusEvent) => void;\n  private _bottomBoundaryFocusListener: (e: FocusEvent) => void;\n\n  /**\n   * This queue has a character pushed to it for keys that are pressed, if the\n   * next character added to the terminal is equal to the key char then it is\n   * not announced (added to live region) because it has already been announced\n   * by the textarea event (which cannot be canceled). There are some race\n   * condition cases if there is typing while data is streaming, but this covers\n   * the main case of typing into the prompt and inputting the answer to a\n   * question (Y/N, etc.).\n   */\n  private _charsToConsume: string[] = [];\n\n  private _charsToAnnounce: string = '';\n\n  constructor(\n    private readonly _terminal: ITerminal,\n    @IInstantiationService instantiationService: IInstantiationService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n    @IRenderService private readonly _renderService: IRenderService\n  ) {\n    super();\n    const doc = this._coreBrowserService.mainDocument;\n    this._accessibilityContainer = doc.createElement('div');\n    this._accessibilityContainer.classList.add('xterm-accessibility');\n\n    this._rowContainer = doc.createElement('div');\n    this._rowContainer.setAttribute('role', 'list');\n    this._rowContainer.classList.add('xterm-accessibility-tree');\n    this._rowElements = [];\n    for (let i = 0; i < this._terminal.rows; i++) {\n      this._rowElements[i] = this._createAccessibilityTreeNode();\n      this._rowContainer.appendChild(this._rowElements[i]);\n    }\n\n    this._topBoundaryFocusListener = e => this._handleBoundaryFocus(e, BoundaryPosition.TOP);\n    this._bottomBoundaryFocusListener = e => this._handleBoundaryFocus(e, BoundaryPosition.BOTTOM);\n    this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);\n    this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n    this._accessibilityContainer.appendChild(this._rowContainer);\n\n    this._liveRegion = doc.createElement('div');\n    this._liveRegion.classList.add('live-region');\n    this._liveRegion.setAttribute('aria-live', 'assertive');\n    this._accessibilityContainer.appendChild(this._liveRegion);\n    this._liveRegionDebouncer = this._register(new TimeBasedDebouncer(this._renderRows.bind(this)));\n\n    if (!this._terminal.element) {\n      throw new Error('Cannot enable accessibility before Terminal.open');\n    }\n\n    if (DEBUG) {\n      this._accessibilityContainer.classList.add('debug');\n      this._rowContainer.classList.add('debug');\n\n      // Use a `<div class=\"xterm\">` container so that the css will still apply.\n      this._debugRootContainer = doc.createElement('div');\n      this._debugRootContainer.classList.add('xterm');\n\n      this._debugRootContainer.appendChild(doc.createTextNode('------start a11y------'));\n      this._debugRootContainer.appendChild(this._accessibilityContainer);\n      this._debugRootContainer.appendChild(doc.createTextNode('------end a11y------'));\n\n      this._terminal.element.insertAdjacentElement('afterend', this._debugRootContainer);\n    } else {\n      this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityContainer);\n    }\n\n    this._register(this._terminal.onResize(e => this._handleResize(e.rows)));\n    this._register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));\n    this._register(this._terminal.onScroll(() => this._refreshRows()));\n    // Line feed is an issue as the prompt won't be read out after a command is run\n    this._register(this._terminal.onA11yChar(char => this._handleChar(char)));\n    this._register(this._terminal.onLineFeed(() => this._handleChar('\\n')));\n    this._register(this._terminal.onA11yTab(spaceCount => this._handleTab(spaceCount)));\n    this._register(this._terminal.onKey(e => this._handleKey(e.key)));\n    this._register(this._terminal.onBlur(() => this._clearLiveRegion()));\n    this._register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));\n    this._register(addDisposableListener(doc, 'selectionchange', () => this._handleSelectionChange()));\n    this._register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));\n\n    this._refreshRowsDimensions();\n    this._refreshRows();\n    this._register(toDisposable(() => {\n      if (DEBUG) {\n        this._debugRootContainer!.remove();\n      } else {\n        this._accessibilityContainer.remove();\n      }\n      this._rowElements.length = 0;\n    }));\n  }\n\n  private _handleTab(spaceCount: number): void {\n    for (let i = 0; i < spaceCount; i++) {\n      this._handleChar(' ');\n    }\n  }\n\n  private _handleChar(char: string): void {\n    if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {\n      if (this._charsToConsume.length > 0) {\n        // Have the screen reader ignore the char if it was just input\n        const shiftedChar = this._charsToConsume.shift();\n        if (shiftedChar !== char) {\n          this._charsToAnnounce += char;\n        }\n      } else {\n        this._charsToAnnounce += char;\n      }\n\n      if (char === '\\n') {\n        this._liveRegionLineCount++;\n        if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {\n          this._liveRegion.textContent = Strings.tooMuchOutput.get();\n        }\n      }\n    }\n  }\n\n  private _clearLiveRegion(): void {\n    this._liveRegion.textContent = '';\n    this._liveRegionLineCount = 0;\n  }\n\n  private _handleKey(keyChar: string): void {\n    this._clearLiveRegion();\n    // Only add the char if there is no control character.\n    if (!/\\p{Control}/u.test(keyChar)) {\n      this._charsToConsume.push(keyChar);\n    }\n  }\n\n  private _refreshRows(start?: number, end?: number): void {\n    this._liveRegionDebouncer.refresh(start, end, this._terminal.rows);\n  }\n\n  private _renderRows(start: number, end: number): void {\n    const buffer: IBuffer = this._terminal.buffer;\n    const setSize = buffer.lines.length.toString();\n    for (let i = start; i <= end; i++) {\n      const line = buffer.lines.get(buffer.ydisp + i);\n      const columns: number[] = [];\n      const lineData = line?.translateToString(true, undefined, undefined, columns) || '';\n      const posInSet = (buffer.ydisp + i + 1).toString();\n      const element = this._rowElements[i];\n      if (element) {\n        if (lineData.length === 0) {\n          element.textContent = '\\u00a0';\n          this._rowColumns.set(element, [0, 1]);\n        } else {\n          element.textContent = lineData;\n          this._rowColumns.set(element, columns);\n        }\n        element.setAttribute('aria-posinset', posInSet);\n        element.setAttribute('aria-setsize', setSize);\n        this._alignRowWidth(element);\n      }\n    }\n    this._announceCharacters();\n  }\n\n  private _announceCharacters(): void {\n    if (this._charsToAnnounce.length === 0) {\n      return;\n    }\n    if (this._liveRegion.textContent === Strings.tooMuchOutput.get()) {\n      this._clearLiveRegion();\n    }\n    this._liveRegion.textContent += this._charsToAnnounce;\n    this._charsToAnnounce = '';\n  }\n\n  private _handleBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {\n    const boundaryElement = e.target as HTMLElement;\n    const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];\n\n    // Don't scroll if the buffer top has reached the end in that direction\n    const posInSet = boundaryElement.getAttribute('aria-posinset');\n    const lastRowPos = position === BoundaryPosition.TOP ? '1' : `${this._terminal.buffer.lines.length}`;\n    if (posInSet === lastRowPos) {\n      return;\n    }\n\n    // Don't scroll when the last focused item was not the second row (focus is going the other\n    // direction)\n    if (e.relatedTarget !== beforeBoundaryElement) {\n      return;\n    }\n\n    // Remove old boundary element from array\n    let topBoundaryElement: HTMLElement;\n    let bottomBoundaryElement: HTMLElement;\n    if (position === BoundaryPosition.TOP) {\n      topBoundaryElement = boundaryElement;\n      bottomBoundaryElement = this._rowElements.pop()!;\n      this._rowContainer.removeChild(bottomBoundaryElement);\n    } else {\n      topBoundaryElement = this._rowElements.shift()!;\n      bottomBoundaryElement = boundaryElement;\n      this._rowContainer.removeChild(topBoundaryElement);\n    }\n\n    // Remove listeners from old boundary elements\n    topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener);\n    bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);\n\n    // Add new element to array/DOM\n    if (position === BoundaryPosition.TOP) {\n      const newElement = this._createAccessibilityTreeNode();\n      this._rowElements.unshift(newElement);\n      this._rowContainer.insertAdjacentElement('afterbegin', newElement);\n    } else {\n      const newElement = this._createAccessibilityTreeNode();\n      this._rowElements.push(newElement);\n      this._rowContainer.appendChild(newElement);\n    }\n\n    // Add listeners to new boundary elements\n    this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);\n    this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n    // Scroll up\n    this._terminal.scrollLines(position === BoundaryPosition.TOP ? -1 : 1);\n\n    // Focus new boundary before element\n    this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2].focus();\n\n    // Prevent the standard behavior\n    e.preventDefault();\n    e.stopImmediatePropagation();\n  }\n\n  private _handleSelectionChange(): void {\n    if (this._rowElements.length === 0) {\n      return;\n    }\n\n    const selection = this._coreBrowserService.mainDocument.getSelection();\n    if (!selection) {\n      return;\n    }\n\n    if (selection.isCollapsed) {\n      // Only do something when the anchorNode is inside the row container. This\n      // behavior mirrors what we do with mouse --- if the mouse clicks\n      // somewhere outside of the terminal, we don't clear the selection.\n      if (this._rowContainer.contains(selection.anchorNode)) {\n        this._terminal.clearSelection();\n      }\n      return;\n    }\n\n    if (!selection.anchorNode || !selection.focusNode) {\n      console.error('anchorNode and/or focusNode are null');\n      return;\n    }\n\n    // Sort the two selection points in document order.\n    let begin = { node: selection.anchorNode, offset: selection.anchorOffset };\n    let end = { node: selection.focusNode, offset: selection.focusOffset };\n    if ((begin.node.compareDocumentPosition(end.node) & Node.DOCUMENT_POSITION_PRECEDING) || (begin.node === end.node && begin.offset > end.offset) ) {\n      [begin, end] = [end, begin];\n    }\n\n    // Clamp begin/end to the inside of the row container.\n    if (begin.node.compareDocumentPosition(this._rowElements[0]) & (Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING)) {\n      begin = { node: this._rowElements[0].childNodes[0], offset: 0 };\n    }\n    if (!this._rowContainer.contains(begin.node)) {\n      // This happens when `begin` is below the last row.\n      return;\n    }\n    const lastRowElement = this._rowElements.slice(-1)[0];\n    if (end.node.compareDocumentPosition(lastRowElement) & (Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_PRECEDING)) {\n      end = {\n        node: lastRowElement,\n        offset: lastRowElement.textContent?.length ?? 0\n      };\n    }\n    if (!this._rowContainer.contains(end.node)) {\n      // This happens when `end` is above the first row.\n      return;\n    }\n\n    const toRowColumn = ({ node, offset }: typeof begin): {row: number, column: number} | null => {\n      // `node` is either the row element or the Text node inside it.\n      const rowElement: any = node instanceof Text ? node.parentNode : node;\n      let row = parseInt(rowElement?.getAttribute('aria-posinset'), 10) - 1;\n      if (isNaN(row)) {\n        console.warn('row is invalid. Race condition?');\n        return null;\n      }\n\n      const columns = this._rowColumns.get(rowElement);\n      if (!columns) {\n        console.warn('columns is null. Race condition?');\n        return null;\n      }\n\n      let column = offset < columns.length ? columns[offset] : columns.slice(-1)[0] + 1;\n      if (column >= this._terminal.cols) {\n        ++row;\n        column = 0;\n      }\n      return {\n        row,\n        column\n      };\n    };\n\n    const beginRowColumn = toRowColumn(begin);\n    const endRowColumn = toRowColumn(end);\n\n    if (!beginRowColumn || !endRowColumn) {\n      return;\n    }\n\n    if (beginRowColumn.row > endRowColumn.row || (beginRowColumn.row === endRowColumn.row && beginRowColumn.column >= endRowColumn.column)) {\n      // This should not happen unless we have some bugs.\n      throw new Error('invalid range');\n    }\n\n    this._terminal.select(\n      beginRowColumn.column,\n      beginRowColumn.row,\n      (endRowColumn.row - beginRowColumn.row) * this._terminal.cols - beginRowColumn.column + endRowColumn.column\n    );\n  }\n\n  private _handleResize(rows: number): void {\n    // Remove bottom boundary listener\n    this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);\n\n    // Grow rows as required\n    for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {\n      this._rowElements[i] = this._createAccessibilityTreeNode();\n      this._rowContainer.appendChild(this._rowElements[i]);\n    }\n    // Shrink rows as required\n    while (this._rowElements.length > rows) {\n      this._rowContainer.removeChild(this._rowElements.pop()!);\n    }\n\n    // Add bottom boundary listener\n    this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n    this._refreshRowsDimensions();\n  }\n\n  private _createAccessibilityTreeNode(): HTMLElement {\n    const element = this._coreBrowserService.mainDocument.createElement('div');\n    element.setAttribute('role', 'listitem');\n    element.tabIndex = -1;\n    this._refreshRowDimensions(element);\n    return element;\n  }\n\n  private _refreshRowsDimensions(): void {\n    if (!this._renderService.dimensions.css.cell.height) {\n      return;\n    }\n    Object.assign(this._accessibilityContainer.style, {\n      width: `${this._renderService.dimensions.css.canvas.width}px`,\n      fontSize: `${this._terminal.options.fontSize}px`\n    });\n    if (this._rowElements.length !== this._terminal.rows) {\n      this._handleResize(this._terminal.rows);\n    }\n    for (let i = 0; i < this._terminal.rows; i++) {\n      this._refreshRowDimensions(this._rowElements[i]);\n      this._alignRowWidth(this._rowElements[i]);\n    }\n  }\n\n  private _refreshRowDimensions(element: HTMLElement): void {\n    element.style.height = `${this._renderService.dimensions.css.cell.height}px`;\n  }\n\n  /**\n   * Scale the width of a row so that each of the character is (mostly) aligned\n   * with the actual rendering. This will allow the screen reader to draw\n   * selection outline at the correct position.\n   *\n   * On top of using the \"monospace\" font and correct font size, the scaling\n   * here is necessary to handle characters that are not covered by the font\n   * (e.g. CJK).\n   */\n  private _alignRowWidth(element: HTMLElement): void {\n    element.style.transform = '';\n    const width = element.getBoundingClientRect().width;\n    const lastColumn = this._rowColumns.get(element)?.slice(-1)?.[0];\n    if (!lastColumn) {\n      return;\n    }\n    const targetWidth = lastColumn * this._renderService.dimensions.css.cell.width;\n    element.style.transform = `scaleX(${targetWidth / width})`;\n  }\n}\n"
  },
  {
    "path": "src/browser/Clipboard.test.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport * as Clipboard from 'browser/Clipboard';\n\ndescribe('evaluatePastedTextProcessing', () => {\n  it('should replace carriage return and/or line feed with carriage return', () => {\n    const pastedText = {\n      unix: 'foo\\nbar\\n',\n      windows: 'foo\\r\\nbar\\r\\n'\n    };\n\n    const processedText = {\n      unix: Clipboard.prepareTextForTerminal(pastedText.unix),\n      windows: Clipboard.prepareTextForTerminal(pastedText.windows)\n    };\n\n    assert.equal(processedText.unix, 'foo\\rbar\\r');\n    assert.equal(processedText.windows, 'foo\\rbar\\r');\n  });\n  it('should bracket pasted text in bracketedPasteMode', () => {\n    const pastedText = 'foo bar';\n    const unbracketedText = Clipboard.bracketTextForPaste(pastedText, false);\n    const bracketedText = Clipboard.bracketTextForPaste(pastedText, true);\n\n    assert.equal(unbracketedText, 'foo bar');\n    assert.equal(bracketedText, '\\x1b[200~foo bar\\x1b[201~');\n  });\n\n  it('should escape embedded escape sequences in pasted text only when bracketed', () => {\n    const ESC_SYMBOL = '\\u241b';\n    const pastedText = '\\x1b[201~foo\\x1b[200~bar';\n    const unbracketedText = Clipboard.bracketTextForPaste(pastedText, false);\n    const bracketedText = Clipboard.bracketTextForPaste(pastedText, true);\n\n    assert.equal(unbracketedText, pastedText, 'non bracketed paste should remain unchanged');\n    assert.equal(bracketedText, `\\x1b[200~${ESC_SYMBOL}[201~foo${ESC_SYMBOL}[200~bar\\x1b[201~`);\n  });\n});\n"
  },
  {
    "path": "src/browser/Clipboard.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ISelectionService } from 'browser/services/Services';\nimport { ICoreService, IOptionsService } from 'common/services/Services';\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string): string {\n  return text.replace(/\\r?\\n/g, '\\r');\n}\n\n/**\n * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste\n * @param text The pasted text to bracket\n */\nexport function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {\n  if (!bracketedPasteMode) {\n    return text;\n  }\n  // Sanitize pasted text to prevent injected escape sequences (e.g. exiting bracketed paste)\n  // by replacing ESC (\\x1b) with its visible representation U+241B (␛).\n  const sanitizedText = text.replace(/\\x1b/g, '\\u241b');\n  return `\\x1b[200~${sanitizedText}\\x1b[201~`;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, selectionService: ISelectionService): void {\n  if (ev.clipboardData) {\n    ev.clipboardData.setData('text/plain', selectionService.selectionText);\n  }\n  // Prevent or the original text will be copied.\n  ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n */\nexport function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService, optionsService: IOptionsService): void {\n  ev.stopPropagation();\n  if (ev.clipboardData) {\n    const text = ev.clipboardData.getData('text/plain');\n    paste(text, textarea, coreService, optionsService);\n  }\n}\n\nexport function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService, optionsService: IOptionsService): void {\n  text = prepareTextForTerminal(text);\n  text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode && optionsService.rawOptions.ignoreBracketedPasteMode !== true);\n  coreService.triggerDataEvent(text, true);\n  textarea.value = '';\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void {\n\n  // Calculate textarea position relative to the screen element\n  const pos = screenElement.getBoundingClientRect();\n  const left = ev.clientX - pos.left - 10;\n  const top = ev.clientY - pos.top - 10;\n\n  // Bring textarea at the cursor position\n  textarea.style.width = '20px';\n  textarea.style.height = '20px';\n  textarea.style.left = `${left}px`;\n  textarea.style.top = `${top}px`;\n  textarea.style.zIndex = '1000';\n\n  textarea.focus();\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void {\n  moveTextAreaUnderMouseCursor(ev, textarea, screenElement);\n\n  if (shouldSelectWord) {\n    selectionService.rightClickSelect(ev);\n  }\n\n  // Get textarea ready to copy from the context menu\n  textarea.value = selectionService.selectionText;\n  textarea.select();\n}\n"
  },
  {
    "path": "src/browser/ColorContrastCache.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { ColorContrastCache } from 'browser/ColorContrastCache';\n\ndescribe('ColorContrastCache', () => {\n  let cache: ColorContrastCache;\n\n  beforeEach(() => {\n    cache = new ColorContrastCache();\n  });\n\n  it('should save and get color values', () => {\n    assert.strictEqual(cache.getColor(0x01, 0x00), undefined);\n    cache.setColor(0x01, 0x01, null);\n    assert.strictEqual(cache.getColor(0x01, 0x01), null);\n    cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});\n    assert.deepEqual(cache.getColor(0x01, 0x02), { css: '#030303', rgba: 0x030303ff});\n  });\n\n  it('should save and get css values', () => {\n    assert.strictEqual(cache.getCss(0x01, 0x00), undefined);\n    cache.setCss(0x01, 0x01, null);\n    assert.strictEqual(cache.getCss(0x01, 0x01), null);\n    cache.setCss(0x01, 0x02, '#030303');\n    assert.deepEqual(cache.getCss(0x01, 0x02), '#030303');\n  });\n\n  it('should clear all values on clear', () => {\n    cache.setColor(0x01, 0x01, null);\n    cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});\n    cache.setCss(0x01, 0x01, null);\n    cache.setCss(0x01, 0x02, '#030303');\n    cache.clear();\n    assert.strictEqual(cache.getColor(0x01, 0x01), undefined);\n    assert.strictEqual(cache.getColor(0x01, 0x02), undefined);\n    assert.strictEqual(cache.getCss(0x01, 0x01), undefined);\n    assert.strictEqual(cache.getCss(0x01, 0x02), undefined);\n  });\n});\n"
  },
  {
    "path": "src/browser/ColorContrastCache.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorContrastCache } from 'browser/Types';\nimport { IColor } from 'common/Types';\nimport { TwoKeyMap } from 'common/MultiKeyMap';\n\nexport class ColorContrastCache implements IColorContrastCache {\n  private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();\n  private _css: TwoKeyMap</* bg */number, /* fg */number, string | null> = new TwoKeyMap();\n\n  public setCss(bg: number, fg: number, value: string | null): void {\n    this._css.set(bg, fg, value);\n  }\n\n  public getCss(bg: number, fg: number): string | null | undefined {\n    return this._css.get(bg, fg);\n  }\n\n  public setColor(bg: number, fg: number, value: IColor | null): void {\n    this._color.set(bg, fg, value);\n  }\n\n  public getColor(bg: number, fg: number): IColor | null | undefined {\n    return this._color.get(bg, fg);\n  }\n\n  public clear(): void {\n    this._color.clear();\n    this._css.clear();\n  }\n}\n"
  },
  {
    "path": "src/browser/CoreBrowserTerminal.ts",
    "content": "/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n *   Fabrice Bellard's javascript vt100 for jslinux:\n *   http://bellard.org/jslinux/\n *   Copyright (c) 2011 Fabrice Bellard\n *   The original design remains. The terminal itself\n *   has been extended to include xterm CSI codes, among\n *   other features.\n *\n * Terminal Emulation References:\n *   http://vt100.net/\n *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n *   http://invisible-island.net/vttest/\n *   http://www.inwap.com/pdp10/ansicode.txt\n *   http://linux.die.net/man/4/console_codes\n *   http://linux.die.net/man/7/urxvt\n */\n\nimport { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';\nimport { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';\nimport * as Strings from 'browser/LocalizableStrings';\nimport { OscLinkProvider } from 'browser/OscLinkProvider';\nimport { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types';\nimport { Viewport } from 'browser/Viewport';\nimport { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';\nimport { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';\nimport { CompositionHelper } from 'browser/input/CompositionHelper';\nimport { DomRenderer } from 'browser/renderer/dom/DomRenderer';\nimport { IRenderer } from 'browser/renderer/shared/Types';\nimport { CharSizeService } from 'browser/services/CharSizeService';\nimport { CharacterJoinerService } from 'browser/services/CharacterJoinerService';\nimport { CoreBrowserService } from 'browser/services/CoreBrowserService';\nimport { LinkProviderService } from 'browser/services/LinkProviderService';\nimport { MouseCoordsService } from 'browser/services/MouseCoordsService';\nimport { MouseService } from 'browser/services/MouseService';\nimport { RenderService } from 'browser/services/RenderService';\nimport { SelectionService } from 'browser/services/SelectionService';\nimport { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';\nimport { ThemeService } from 'browser/services/ThemeService';\nimport { KeyboardService } from 'browser/services/KeyboardService';\nimport { channels, color, rgb } from 'common/Color';\nimport { CoreTerminal } from 'common/CoreTerminal';\nimport * as Browser from 'common/Platform';\nimport { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from 'common/Types';\nimport { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { IBuffer } from 'common/buffer/Types';\nimport { C0, C1ESCAPED } from 'common/data/EscapeSequences';\nimport { toRgbString } from 'common/input/XParseColor';\nimport { DecorationService } from 'common/services/DecorationService';\nimport { IDecorationService } from 'common/services/Services';\nimport { WindowsOptionsReportType } from '../common/InputHandler';\nimport { AccessibilityManager } from './AccessibilityManager';\nimport { Linkifier } from './Linkifier';\nimport { Emitter, EventUtils, type IEvent } from 'common/Event';\nimport { addDisposableListener } from 'browser/Dom';\nimport { MutableDisposable, toDisposable } from 'common/Lifecycle';\n\nexport class CoreBrowserTerminal extends CoreTerminal implements ITerminal {\n  public textarea: HTMLTextAreaElement | undefined;\n  public element: HTMLElement | undefined;\n  public screenElement: HTMLElement | undefined;\n\n  private _document: Document | undefined;\n  private _viewportElement: HTMLElement | undefined;\n  private _helperContainer: HTMLElement | undefined;\n  private _compositionView: HTMLElement | undefined;\n\n  private readonly _linkifier: MutableDisposable<ILinkifier2> = this._register(new MutableDisposable());\n  public get linkifier(): ILinkifier2 | undefined { return this._linkifier.value; }\n  private _overviewRulerRenderer: OverviewRulerRenderer | undefined;\n  private _viewport: Viewport | undefined;\n\n  public browser: IBrowser = Browser as any;\n\n  private _customKeyEventHandler: CustomKeyEventHandler | undefined;\n\n  // Browser services\n  private readonly _decorationService: DecorationService;\n  private readonly _keyboardService: IKeyboardService;\n  private readonly _linkProviderService: ILinkProviderService;\n\n  // Optional browser services\n  private _charSizeService: ICharSizeService | undefined;\n  private _coreBrowserService: ICoreBrowserService | undefined;\n  private _mouseCoordsService: IMouseCoordsService | undefined;\n  private _mouseService: IMouseService | undefined;\n  private _renderService: IRenderService | undefined;\n  private _themeService: IThemeService | undefined;\n  private _characterJoinerService: ICharacterJoinerService | undefined;\n  private _selectionService: ISelectionService | undefined;\n\n  /**\n   * Records whether the keydown event has already been handled and triggered a data event, if so\n   * the keypress event should not trigger a data event but should still print to the textarea so\n   * screen readers will announce it.\n   */\n  private _keyDownHandled: boolean = false;\n\n  /**\n   * Records whether a keydown event has occured since the last keyup event, i.e. whether a key\n   * is currently \"pressed\".\n   */\n  private _keyDownSeen: boolean = false;\n\n  /**\n   * Records whether the keypress event has already been handled and triggered a data event, if so\n   * the input event should not trigger a data event but should still print to the textarea so\n   * screen readers will announce it.\n   */\n  private _keyPressHandled: boolean = false;\n\n  /**\n   * Records whether there has been a keydown event for a dead key without a corresponding keydown\n   * event for the composed/alternative character. If we cancel the keydown event for the dead key,\n   * no events will be emitted for the final character.\n   */\n  private _unprocessedDeadKey: boolean = false;\n\n  private _compositionHelper: ICompositionHelper | undefined;\n  private _accessibilityManager: MutableDisposable<AccessibilityManager> = this._register(new MutableDisposable());\n\n  private readonly _onCursorMove = this._register(new Emitter<void>());\n  public readonly onCursorMove = this._onCursorMove.event;\n  private readonly _onKey = this._register(new Emitter<{ key: string, domEvent: KeyboardEvent }>());\n  public readonly onKey = this._onKey.event;\n  private readonly _onSelectionChange = this._register(new Emitter<void>());\n  public readonly onSelectionChange = this._onSelectionChange.event;\n  private readonly _onTitleChange = this._register(new Emitter<string>());\n  public readonly onTitleChange = this._onTitleChange.event;\n  private readonly _onBell = this._register(new Emitter<void>());\n  public readonly onBell = this._onBell.event;\n\n  private _onFocus = this._register(new Emitter<void>());\n  public get onFocus(): IEvent<void> { return this._onFocus.event; }\n  private _onBlur = this._register(new Emitter<void>());\n  public get onBlur(): IEvent<void> { return this._onBlur.event; }\n  private _onA11yCharEmitter = this._register(new Emitter<string>());\n  public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }\n  private _onA11yTabEmitter = this._register(new Emitter<number>());\n  public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }\n  private _onWillOpen = this._register(new Emitter<HTMLElement>());\n  public get onWillOpen(): IEvent<HTMLElement> { return this._onWillOpen.event; }\n  private readonly _onDimensionsChange = this._register(new Emitter<IRenderDimensionsApi>());\n  public readonly onDimensionsChange = this._onDimensionsChange.event;\n\n  public get dimensions(): IRenderDimensionsApi | undefined {\n    if (!this._renderService) {\n      return undefined;\n    }\n    const dimensions = this._renderService.dimensions;\n    return {\n      css: {\n        canvas: { ...dimensions.css.canvas },\n        cell: { ...dimensions.css.cell }\n      },\n      device: {\n        canvas: { ...dimensions.device.canvas },\n        cell: { ...dimensions.device.cell },\n        char: { ...dimensions.device.char }\n      }\n    };\n  }\n\n  constructor(\n    options: Partial<ITerminalOptions> = {}\n  ) {\n    super(options);\n\n    this._setup();\n\n    this._decorationService = this._instantiationService.createInstance(DecorationService);\n    this._instantiationService.setService(IDecorationService, this._decorationService);\n    this._keyboardService = this._instantiationService.createInstance(KeyboardService);\n    this._instantiationService.setService(IKeyboardService, this._keyboardService);\n    this._linkProviderService = this._instantiationService.createInstance(LinkProviderService);\n    this._instantiationService.setService(ILinkProviderService, this._linkProviderService);\n    this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));\n\n    // Setup InputHandler listeners\n    this._register(this._inputHandler.onRequestBell(() => this._onBell.fire()));\n    this._register(this._inputHandler.onRequestRefreshRows((e) => this.refresh(e?.start ?? 0, e?.end ?? (this.rows - 1))));\n    this._register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));\n    this._register(this._inputHandler.onRequestReset(() => this.reset()));\n    this._register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));\n    this._register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));\n    this._register(EventUtils.forward(this._inputHandler.onCursorMove, this._onCursorMove));\n    this._register(EventUtils.forward(this._inputHandler.onTitleChange, this._onTitleChange));\n    this._register(EventUtils.forward(this._inputHandler.onA11yChar, this._onA11yCharEmitter));\n    this._register(EventUtils.forward(this._inputHandler.onA11yTab, this._onA11yTabEmitter));\n\n    // Setup listeners\n    this._register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));\n\n    this._register(toDisposable(() => {\n      this._customKeyEventHandler = undefined;\n      this.element?.parentNode?.removeChild(this.element);\n    }));\n  }\n\n  /**\n   * Handle color event from inputhandler for OSC 4|104 | 10|110 | 11|111 | 12|112.\n   * An event from OSC 4|104 may contain multiple set or report requests, and multiple\n   * or none restore requests (resetting all),\n   * while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.\n   */\n  private _handleColorEvent(event: IColorEvent): void {\n    if (!this._themeService) return;\n    for (const req of event) {\n      let acc: 'foreground' | 'background' | 'cursor' | 'ansi';\n      let ident = '';\n      switch (req.index) {\n        case SpecialColorIndex.FOREGROUND: // OSC 10 | 110\n          acc = 'foreground';\n          ident = '10';\n          break;\n        case SpecialColorIndex.BACKGROUND: // OSC 11 | 111\n          acc = 'background';\n          ident = '11';\n          break;\n        case SpecialColorIndex.CURSOR: // OSC 12 | 112\n          acc = 'cursor';\n          ident = '12';\n          break;\n        default: // OSC 4 | 104\n          // we can skip the [0..255] range check here (already done in inputhandler)\n          acc = 'ansi';\n          ident = '4;' + req.index;\n      }\n      switch (req.type) {\n        case ColorRequestType.REPORT:\n          const colorRgb = color.toColorRGB(acc === 'ansi'\n            ? this._themeService.colors.ansi[req.index]\n            : this._themeService.colors[acc]);\n          this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(colorRgb)}${C1ESCAPED.ST}`);\n          break;\n        case ColorRequestType.SET:\n          if (acc === 'ansi') {\n            this._themeService.modifyColors(colors => colors.ansi[req.index] = channels.toColor(...req.color));\n          } else {\n            const narrowedAcc = acc;\n            this._themeService.modifyColors(colors => colors[narrowedAcc] = channels.toColor(...req.color));\n          }\n          break;\n        case ColorRequestType.RESTORE:\n          this._themeService.restoreColor(req.index);\n          break;\n      }\n    }\n  }\n\n  /**\n   * Reports the current color scheme (dark or light) based on the relative luminance\n   * of the background and foreground theme colors.\n   * Sends CSI ? 997 ; 1 n for dark mode or CSI ? 997 ; 2 n for light mode.\n   */\n  private _reportColorScheme(): void {\n    if (!this._themeService) return;\n    const bgLuminance = rgb.relativeLuminance(this._themeService.colors.background.rgba >> 8);\n    const fgLuminance = rgb.relativeLuminance(this._themeService.colors.foreground.rgba >> 8);\n    // Dark mode = background is darker than foreground (lower luminance)\n    const colorSchemeMode = bgLuminance < fgLuminance ? 1 : 2;\n    this.coreService.triggerDataEvent(`${C0.ESC}[?997;${colorSchemeMode}n`);\n  }\n\n  protected _setup(): void {\n    super._setup();\n\n    this._customKeyEventHandler = undefined;\n  }\n\n  /**\n   * Convenience property to active buffer.\n   */\n  public get buffer(): IBuffer {\n    return this.buffers.active;\n  }\n\n  /**\n   * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n   */\n  public focus(): void {\n    if (this.textarea) {\n      this.textarea.focus({ preventScroll: true });\n    }\n  }\n\n  private _handleScreenReaderModeOptionChange(value: boolean): void {\n    if (value) {\n      if (!this._accessibilityManager.value && this._renderService) {\n        this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);\n      }\n    } else {\n      this._accessibilityManager.clear();\n    }\n  }\n\n  /**\n   * Binds the desired focus behavior on a given terminal object.\n   */\n  private _handleTextAreaFocus(ev: FocusEvent): void {\n    if (this.coreService.decPrivateModes.sendFocus) {\n      this.coreService.triggerDataEvent(C0.ESC + '[I');\n    }\n    this.element!.classList.add('focus');\n    this._showCursor();\n    this._onFocus.fire();\n  }\n\n  /**\n   * Blur the terminal, calling the blur function on the terminal's underlying\n   * textarea.\n   */\n  public blur(): void {\n    return this.textarea?.blur();\n  }\n\n  /**\n   * Binds the desired blur behavior on a given terminal object.\n   */\n  private _handleTextAreaBlur(): void {\n    // Text can safely be removed on blur. Doing it earlier could interfere with\n    // screen readers reading it out.\n    this.textarea!.value = '';\n    this.refresh(this.buffer.y, this.buffer.y);\n    if (this.coreService.decPrivateModes.sendFocus) {\n      this.coreService.triggerDataEvent(C0.ESC + '[O');\n    }\n    this.element!.classList.remove('focus');\n    this._onBlur.fire();\n  }\n\n  private _syncTextArea(): void {\n    if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing || !this._renderService) {\n      return;\n    }\n    const cursorY = this.buffer.ybase + this.buffer.y;\n    const bufferLine = this.buffer.lines.get(cursorY);\n    if (!bufferLine) {\n      return;\n    }\n    const cursorX = Math.min(this.buffer.x, this.cols - 1);\n    const cellHeight = this._renderService.dimensions.css.cell.height;\n    const width = bufferLine.getWidth(cursorX);\n    const cellWidth = this._renderService.dimensions.css.cell.width * width;\n    const cursorTop = this.buffer.y * this._renderService.dimensions.css.cell.height;\n    const cursorLeft = cursorX * this._renderService.dimensions.css.cell.width;\n\n    // Sync the textarea to the exact position of the composition view so the IME knows where the\n    // text is.\n    this.textarea.style.left = cursorLeft + 'px';\n    this.textarea.style.top = cursorTop + 'px';\n    this.textarea.style.width = cellWidth + 'px';\n    this.textarea.style.height = cellHeight + 'px';\n    this.textarea.style.lineHeight = cellHeight + 'px';\n    this.textarea.style.zIndex = '-5';\n  }\n\n  /**\n   * Initialize default behavior\n   */\n  private _initGlobal(): void {\n    this._bindKeys();\n\n    // Bind clipboard functionality\n    this._register(addDisposableListener(this.element!, 'copy', (event: ClipboardEvent) => {\n      // If mouse events are active it means the selection manager is disabled and\n      // copy should be handled by the host program.\n      if (!this.hasSelection()) {\n        return;\n      }\n      copyHandler(event, this._selectionService!);\n    }));\n    const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this.coreService, this.optionsService);\n    this._register(addDisposableListener(this.textarea!, 'paste', pasteHandlerWrapper));\n    this._register(addDisposableListener(this.element!, 'paste', pasteHandlerWrapper));\n\n    // Handle right click context menus\n    if (Browser.isFirefox) {\n      // Firefox doesn't appear to fire the contextmenu event on right click\n      this._register(addDisposableListener(this.element!, 'mousedown', (event: MouseEvent) => {\n        if (event.button === 2) {\n          rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord);\n        }\n      }));\n    } else {\n      this._register(addDisposableListener(this.element!, 'contextmenu', (event: MouseEvent) => {\n        rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord);\n      }));\n    }\n\n    // Move the textarea under the cursor when middle clicking on Linux to ensure\n    // middle click to paste selection works. This only appears to work in Chrome\n    // at the time is writing.\n    if (Browser.isLinux) {\n      // Use auxclick event over mousedown the latter doesn't seem to work. Note\n      // that the regular click event doesn't fire for the middle mouse button.\n      this._register(addDisposableListener(this.element!, 'auxclick', (event: MouseEvent) => {\n        if (event.button === 1) {\n          moveTextAreaUnderMouseCursor(event, this.textarea!, this.screenElement!);\n        }\n      }));\n    }\n  }\n\n  /**\n   * Apply key handling to the terminal\n   */\n  private _bindKeys(): void {\n    this._register(addDisposableListener(this.textarea!, 'keyup', (ev: KeyboardEvent) => this._keyUp(ev), true));\n    this._register(addDisposableListener(this.textarea!, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true));\n    this._register(addDisposableListener(this.textarea!, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true));\n    this._register(addDisposableListener(this.textarea!, 'compositionstart', () => {\n      // Ensure the textarea is synced to the latest cursor location before composition begins. This\n      // is to workaround a problem where highly dynamic TUIs like agentic CLIs reprint agressively\n      // would cause the IME to appear in the wrong position. The theory is that when the IME is\n      // triggered during a partial render the textarea position becomes locked and will not move\n      // until it is hidden and a custom move occurs.\n      this._syncTextArea();\n      this._compositionHelper!.compositionstart();\n      this._compositionHelper!.updateCompositionElements();\n    }));\n    this._register(addDisposableListener(this.textarea!, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper!.compositionupdate(e)));\n    this._register(addDisposableListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend()));\n    this._register(addDisposableListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true));\n    this._register(this.onRender(() => this._compositionHelper!.updateCompositionElements()));\n  }\n\n  /**\n   * Opens the terminal within an element.\n   *\n   * @param parent The element to create the terminal within.\n   */\n  public open(parent: HTMLElement): void {\n    if (!parent) {\n      throw new Error('Terminal requires a parent element.');\n    }\n\n    if (!parent.isConnected) {\n      this._logService.debug('Terminal.open was called on an element that was not attached to the DOM');\n    }\n\n    // If the terminal is already opened\n    if (this.element?.ownerDocument.defaultView && this._coreBrowserService) {\n      // Adjust the window if needed\n      if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) {\n        this._coreBrowserService.window = this.element.ownerDocument.defaultView;\n      }\n      return;\n    }\n\n    this._document = parent.ownerDocument;\n    if (this.options.documentOverride && this.options.documentOverride instanceof Document) {\n      this._document = this.optionsService.rawOptions.documentOverride as Document;\n    }\n\n    // Create main element container\n    this.element = this._document.createElement('div');\n    this.element.dir = 'ltr';   // xterm.css assumes LTR\n    this.element.classList.add('terminal');\n    this.element.classList.add('xterm');\n    this.element.classList.toggle('allow-transparency', this.options.allowTransparency);\n    this._register(this.optionsService.onSpecificOptionChange('allowTransparency', value => this.element!.classList.toggle('allow-transparency', value)));\n    parent.appendChild(this.element);\n\n    // Performance: Use a document fragment to build the terminal\n    // viewport and helper elements detached from the DOM\n    const fragment = this._document.createDocumentFragment();\n    this._viewportElement = this._document.createElement('div');\n    this._viewportElement.classList.add('xterm-viewport');\n    fragment.appendChild(this._viewportElement);\n\n    this.screenElement = this._document.createElement('div');\n    this.screenElement.classList.add('xterm-screen');\n    this._register(addDisposableListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));\n    // Create the container that will hold helpers like the textarea for\n    // capturing DOM Events. Then produce the helpers.\n    this._helperContainer = this._document.createElement('div');\n    this._helperContainer.classList.add('xterm-helpers');\n    this.screenElement.appendChild(this._helperContainer);\n    fragment.appendChild(this.screenElement);\n\n    const textarea = this.textarea = this._document.createElement('textarea');\n    this.textarea.classList.add('xterm-helper-textarea');\n    this.textarea.setAttribute('aria-label', Strings.promptLabel.get());\n    if (!Browser.isChromeOS) {\n      // ChromeVox on ChromeOS does not like this. See\n      // https://issuetracker.google.com/issues/260170397\n      this.textarea.setAttribute('aria-multiline', 'false');\n    }\n    this.textarea.setAttribute('autocorrect', 'off');\n    this.textarea.setAttribute('autocapitalize', 'off');\n    this.textarea.setAttribute('spellcheck', 'false');\n    this.textarea.tabIndex = 0;\n    this._register(this.optionsService.onSpecificOptionChange('disableStdin', () => textarea.readOnly = this.optionsService.rawOptions.disableStdin));\n    this.textarea.readOnly = this.optionsService.rawOptions.disableStdin;\n\n    // Register the core browser service before the generic textarea handlers are registered so it\n    // handles them first. Otherwise the renderers may use the wrong focus state.\n    this._coreBrowserService = this._register(this._instantiationService.createInstance(CoreBrowserService,\n      this.textarea,\n      parent.ownerDocument.defaultView ?? window,\n      // Force unsafe null in node.js environment for tests\n      this._document ?? (typeof window !== 'undefined') ? window.document : null as any\n    ));\n    this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);\n\n    this._register(addDisposableListener(this.textarea, 'focus', (ev: FocusEvent) => this._handleTextAreaFocus(ev)));\n    this._register(addDisposableListener(this.textarea, 'blur', () => this._handleTextAreaBlur()));\n    this._helperContainer.appendChild(this.textarea);\n\n    this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);\n    this._instantiationService.setService(ICharSizeService, this._charSizeService);\n\n    this._themeService = this._instantiationService.createInstance(ThemeService);\n    this._instantiationService.setService(IThemeService, this._themeService);\n\n    // CSI ? 996 n - color scheme query (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)\n    this._register(this._inputHandler.onRequestColorSchemeQuery(() => this._reportColorScheme()));\n\n    // Emit unsolicited color scheme notification on theme change when DECSET 2031 is enabled\n    this._register(this._themeService.onChangeColors(() => {\n      if (this.coreService.decPrivateModes.colorSchemeUpdates) {\n        this._reportColorScheme();\n      }\n    }));\n\n    this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);\n    this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);\n\n    this._renderService = this._register(this._instantiationService.createInstance(RenderService, this.rows, this.screenElement));\n    this._instantiationService.setService(IRenderService, this._renderService);\n    this._register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));\n    this._register(this._renderService.onDimensionsChange(e => this._onDimensionsChange.fire({\n      css: {\n        canvas: { ...e.css.canvas },\n        cell: { ...e.css.cell }\n      },\n      device: {\n        canvas: { ...e.device.canvas },\n        cell: { ...e.device.cell },\n        char: { ...e.device.char }\n      }\n    })));\n    this.onResize(e => this._renderService!.resize(e.cols, e.rows));\n\n    this._compositionView = this._document.createElement('div');\n    this._compositionView.classList.add('composition-view');\n    this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);\n    this._helperContainer.appendChild(this._compositionView);\n\n    this._mouseCoordsService = this._instantiationService.createInstance(MouseCoordsService);\n    this._instantiationService.setService(IMouseCoordsService, this._mouseCoordsService);\n\n    const linkifier = this._linkifier.value = this._register(this._instantiationService.createInstance(Linkifier, this.screenElement));\n\n    // Performance: Add viewport and helper elements from the fragment\n    this.element.appendChild(fragment);\n\n    try {\n      this._onWillOpen.fire(this.element);\n    }\n    catch { /* fails to load addon for some reason */ }\n    if (!this._renderService.hasRenderer()) {\n      this._renderService.setRenderer(this._createRenderer());\n    }\n\n    this._register(this.onCursorMove(() => {\n      this._renderService!.handleCursorMove();\n      this._syncTextArea();\n    }));\n    this._register(this.onResize(() => {\n      this._renderService!.handleResize(this.cols, this.rows);\n      this._syncTextArea();\n    }));\n    this._register(this.onBlur(() => this._renderService!.handleBlur()));\n    this._register(this.onFocus(() => this._renderService!.handleFocus()));\n\n    this._viewport = this._register(this._instantiationService.createInstance(Viewport, this.element, this.screenElement));\n    this._register(this._viewport.onRequestScrollLines(e => {\n      super.scrollLines(e, false);\n      this.refresh(0, this.rows - 1);\n    }));\n\n    this._selectionService = this._register(this._instantiationService.createInstance(SelectionService,\n      this.element,\n      this.screenElement,\n      linkifier\n    ));\n    this._instantiationService.setService(ISelectionService, this._selectionService);\n    this._mouseService = this._instantiationService.createInstance(MouseService);\n    this._instantiationService.setService(IMouseService, this._mouseService);\n    this._register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));\n    this._register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));\n    this._register(this._selectionService.onRequestRedraw(e => this._renderService!.handleSelectionChanged(e.start, e.end, e.columnSelectMode)));\n    this._register(this._selectionService.onLinuxMouseSelection(text => {\n      // If there's a new selection, put it into the textarea, focus and select it\n      // in order to register it as a selection on the OS. This event is fired\n      // only on Linux to enable middle click to paste selection.\n      this.textarea!.value = text;\n      this.textarea!.focus();\n      this.textarea!.select();\n    }));\n    this._register(EventUtils.any(\n      this._onScroll.event,\n      this._inputHandler.onScroll\n    )(() => {\n      this._selectionService!.refresh();\n      this._viewport?.queueSync();\n    }));\n\n    this._register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));\n    this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));\n\n    // apply mouse event classes set by escape codes before terminal was attached\n    if (this.mouseStateService.areMouseEventsActive) {\n      this._selectionService.disable();\n      this.element.classList.add('enable-mouse-events');\n    } else {\n      this._selectionService.enable();\n    }\n\n    if (this.options.screenReaderMode) {\n      // Note that this must be done *after* the renderer is created in order to\n      // ensure the correct order of the dprchange event\n      this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);\n    }\n    this._register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));\n\n    const showScrollbar = this.options.scrollbar?.showScrollbar ?? true;\n    const overviewRulerWidth = this.options.scrollbar?.width;\n    if (showScrollbar && overviewRulerWidth) {\n      this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));\n    }\n    this.optionsService.onSpecificOptionChange('scrollbar', value => {\n      const shouldShow = (value?.showScrollbar ?? true) && !!value?.width;\n      if (!this._overviewRulerRenderer && shouldShow && this._viewportElement && this.screenElement) {\n        this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));\n      }\n    });\n    // Measure the character size\n    this._charSizeService.measure();\n\n    // Setup loop that draws to screen\n    this.refresh(0, this.rows - 1);\n\n    // Initialize global actions that need to be taken on the document.\n    this._initGlobal();\n\n    // Listen for mouse events and translate\n    // them into terminal mouse protocols.\n    this._mouseService.bindMouse({\n      element: this.element!,\n      screenElement: this.screenElement!,\n      document: this._document!,\n      handleTouchScroll: amount => this._viewport?.handleTouchScroll(amount)\n    }, disposable => this._register(disposable), () => this.focus());\n  }\n\n  private _createRenderer(): IRenderer {\n    return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier!);\n  }\n\n  /**\n   * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n   * opportunity.\n   * @param start The row to start from (between 0 and this.rows - 1).\n   * @param end The row to end at (between start and this.rows - 1).\n   */\n  public refresh(start: number, end: number, sync: boolean = false): void {\n    this._renderService?.refreshRows(start, end, sync);\n  }\n\n  /**\n   * Change the cursor style for different selection modes\n   */\n  public updateCursorStyle(ev: KeyboardEvent | MouseEvent): void {\n    if (this._selectionService?.shouldColumnSelect(ev)) {\n      this.element!.classList.add('column-select');\n    } else {\n      this.element!.classList.remove('column-select');\n    }\n  }\n\n  /**\n   * Display the cursor element\n   */\n  private _showCursor(): void {\n    if (!this.coreService.isCursorInitialized) {\n      this.coreService.isCursorInitialized = true;\n      this.refresh(this.buffer.y, this.buffer.y);\n    }\n  }\n\n  public scrollLines(disp: number, suppressScrollEvent?: boolean): void {\n    // All scrollLines methods need to go via the viewport in order to support smooth scroll\n    if (this._viewport) {\n      this._viewport.scrollLines(disp);\n    } else {\n      super.scrollLines(disp, suppressScrollEvent);\n    }\n    this.refresh(0, this.rows - 1);\n  }\n\n  public scrollPages(pageCount: number): void {\n    this.scrollLines(pageCount * (this.rows - 1));\n  }\n\n  public scrollToTop(): void {\n    this.scrollLines(-this._bufferService.buffer.ydisp);\n  }\n\n  public scrollToBottom(disableSmoothScroll?: boolean): void {\n    if (disableSmoothScroll && this._viewport) {\n      this._viewport.scrollToLine(this.buffer.ybase, true);\n    } else {\n      this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);\n    }\n  }\n\n  public scrollToLine(line: number): void {\n    const scrollAmount = line - this._bufferService.buffer.ydisp;\n    if (scrollAmount !== 0) {\n      this.scrollLines(scrollAmount);\n    }\n  }\n\n  public paste(data: string): void {\n    paste(data, this.textarea!, this.coreService, this.optionsService);\n  }\n\n  public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {\n    this._customKeyEventHandler = customKeyEventHandler;\n  }\n\n  public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {\n    this.mouseStateService.setCustomWheelEventHandler(customWheelEventHandler);\n  }\n\n  public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {\n    return this._linkProviderService.registerLinkProvider(linkProvider);\n  }\n\n  public registerCharacterJoiner(handler: CharacterJoinerHandler): number {\n    if (!this._characterJoinerService) {\n      throw new Error('Terminal must be opened first');\n    }\n    const joinerId = this._characterJoinerService.register(handler);\n    this.refresh(0, this.rows - 1);\n    return joinerId;\n  }\n\n  public deregisterCharacterJoiner(joinerId: number): void {\n    if (!this._characterJoinerService) {\n      throw new Error('Terminal must be opened first');\n    }\n    if (this._characterJoinerService.deregister(joinerId)) {\n      this.refresh(0, this.rows - 1);\n    }\n  }\n\n  public get markers(): IMarker[] {\n    return this.buffer.markers;\n  }\n\n  public registerMarker(cursorYOffset: number): IMarker {\n    return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);\n  }\n\n  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {\n    return this._decorationService.registerDecoration(decorationOptions);\n  }\n\n  /**\n   * Gets whether the terminal has an active selection.\n   */\n  public hasSelection(): boolean {\n    return this._selectionService ? this._selectionService.hasSelection : false;\n  }\n\n  /**\n   * Selects text within the terminal.\n   * @param column The column the selection starts at..\n   * @param row The row the selection starts at.\n   * @param length The length of the selection.\n   */\n  public select(column: number, row: number, length: number): void {\n    this._selectionService!.setSelection(column, row, length);\n  }\n\n  /**\n   * Gets the terminal's current selection, this is useful for implementing copy\n   * behavior outside of xterm.js.\n   */\n  public getSelection(): string {\n    return this._selectionService ? this._selectionService.selectionText : '';\n  }\n\n  public getSelectionPosition(): IBufferRange | undefined {\n    if (!this._selectionService || !this._selectionService.hasSelection) {\n      return undefined;\n    }\n\n    return {\n      start: {\n        x: this._selectionService.selectionStart![0],\n        y: this._selectionService.selectionStart![1]\n      },\n      end: {\n        x: this._selectionService.selectionEnd![0],\n        y: this._selectionService.selectionEnd![1]\n      }\n    };\n  }\n\n  /**\n   * Clears the current terminal selection.\n   */\n  public clearSelection(): void {\n    this._selectionService?.clearSelection();\n  }\n\n  /**\n   * Selects all text within the terminal.\n   */\n  public selectAll(): void {\n    this._selectionService?.selectAll();\n  }\n\n  public selectLines(start: number, end: number): void {\n    this._selectionService?.selectLines(start, end);\n  }\n\n  /**\n   * Handle a keydown [KeyboardEvent].\n   *\n   * [KeyboardEvent]: https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n   */\n  protected _keyDown(event: KeyboardEvent): boolean | undefined {\n    this._keyDownHandled = false;\n    this._keyDownSeen = true;\n\n    if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {\n      return false;\n    }\n\n    // Ignore composing with Alt key on Mac when macOptionIsMeta is enabled\n    const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey;\n\n    if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) {\n      if (this.options.scrollOnUserInput && this.buffer.ybase !== this.buffer.ydisp) {\n        this.scrollToBottom(true);\n      }\n      return false;\n    }\n\n    if (!shouldIgnoreComposition && (event.key === 'Dead' || event.key === 'AltGraph')) {\n      this._unprocessedDeadKey = true;\n    }\n\n    const result = this._keyboardService.evaluateKeyDown(event);\n\n    this.updateCursorStyle(event);\n\n    if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) {\n      const scrollCount = this.rows - 1;\n      this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount);\n      event.preventDefault();\n      event.stopPropagation();\n      return false;\n    }\n\n    if (result.type === KeyboardResultType.SELECT_ALL) {\n      this.selectAll();\n    }\n\n    if (this._isThirdLevelShift(this.browser, event)) {\n      return true;\n    }\n\n    if (result.cancel) {\n      // The event is canceled at the end already, is this necessary?\n      event.preventDefault();\n      event.stopPropagation();\n    }\n\n    if (!result.key) {\n      return true;\n    }\n\n    // HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case\n    // letters cannot be input while caps lock is on. Skip this hack when using kitty protocol\n    // or Win32 input mode as they need to send proper sequences for all key events.\n    if (!this._keyboardService.useKitty && !this._keyboardService.useWin32InputMode && event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {\n      if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) {\n        return true;\n      }\n    }\n\n    if (this._unprocessedDeadKey) {\n      this._unprocessedDeadKey = false;\n      return true;\n    }\n\n    // If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers\n    // will announce deleted characters. This will not work 100% of the time but it should cover\n    // most scenarios.\n    if (result.key === C0.ETX || result.key === C0.CR) {\n      this.textarea!.value = '';\n    }\n\n    const wasModifierOnly = this._keyboardService.useWin32InputMode && wasModifierKeyOnlyEvent(event);\n    this._onKey.fire({ key: result.key, domEvent: event });\n    this._showCursor();\n    this.coreService.triggerDataEvent(result.key, !wasModifierOnly);\n\n    // Cancel events when not in screen reader mode so events don't get bubbled up and handled by\n    // other listeners. When screen reader mode is enabled, we don't cancel them (unless ctrl or alt\n    // is also depressed) so that the cursor textarea can be updated, which triggers the screen\n    // reader to read it.\n    if (!this.optionsService.rawOptions.screenReaderMode || event.altKey || event.ctrlKey) {\n      event.preventDefault();\n      event.stopPropagation();\n      return false;\n    }\n\n    this._keyDownHandled = true;\n  }\n\n  private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {\n    const thirdLevelKey =\n      (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n      (browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey) ||\n      (browser.isWindows && ev.getModifierState('AltGraph'));\n\n    if (ev.type === 'keypress') {\n      return thirdLevelKey;\n    }\n\n    // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n    return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n  }\n\n  protected _keyUp(ev: KeyboardEvent): void {\n    this._keyDownSeen = false;\n\n    if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {\n      return;\n    }\n\n    if (!wasModifierKeyOnlyEvent(ev)) {\n      this.focus();\n    }\n\n    // Handle key release for Kitty keyboard protocol\n    const result = this._keyboardService.evaluateKeyUp(ev);\n    if (result?.key) {\n      const wasModifierOnly = this._keyboardService.useWin32InputMode && wasModifierKeyOnlyEvent(ev);\n      this.coreService.triggerDataEvent(result.key, !wasModifierOnly);\n    }\n\n    this.updateCursorStyle(ev);\n    this._keyPressHandled = false;\n  }\n\n  /**\n   * Handle a keypress event.\n   * Key Resources:\n   *   - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n   * @param ev The keypress event to be handled.\n   */\n  protected _keyPress(ev: KeyboardEvent): boolean {\n    let key;\n\n    this._keyPressHandled = false;\n\n    if (this._keyDownHandled) {\n      return false;\n    }\n\n    if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {\n      return false;\n    }\n\n    if (ev.charCode) {\n      key = ev.charCode;\n    } else if (ev.which === null || ev.which === undefined) {\n      key = ev.keyCode;\n    } else if (ev.which !== 0 && ev.charCode !== 0) {\n      key = ev.which;\n    } else {\n      return false;\n    }\n\n    if (!key || (\n      (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)\n    )) {\n      return false;\n    }\n\n    key = String.fromCharCode(key);\n\n    this._onKey.fire({ key, domEvent: ev });\n    this._showCursor();\n    this.coreService.triggerDataEvent(key, true);\n\n    this._keyPressHandled = true;\n\n    // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow\n    // keys could be ignored\n    this._unprocessedDeadKey = false;\n\n    return true;\n  }\n\n  /**\n   * Handle an input event.\n   * Key Resources:\n   *   - https://developer.mozilla.org/en-US/docs/Web/API/InputEvent\n   * @param ev The input event to be handled.\n   */\n  protected _inputEvent(ev: InputEvent): boolean {\n    // Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to\n    // support reading out character input which can doubling up input characters\n    // Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679\n    if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) {\n      if (this._keyPressHandled) {\n        return false;\n      }\n\n      // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow\n      // keys could be ignored\n      this._unprocessedDeadKey = false;\n\n      const text = ev.data;\n      this.coreService.triggerDataEvent(text, true);\n      return true;\n    }\n\n    return false;\n  }\n\n  /**\n   * Resizes the terminal.\n   *\n   * @param x The number of columns to resize to.\n   * @param y The number of rows to resize to.\n   */\n  public resize(x: number, y: number): void {\n    if (x === this.cols && y === this.rows) {\n      // Check if we still need to measure the char size (fixes #785).\n      if (this._charSizeService && !this._charSizeService.hasValidSize) {\n        this._charSizeService.measure();\n      }\n      return;\n    }\n\n    super.resize(x, y);\n  }\n\n  private _afterResize(x: number, y: number): void {\n    this._charSizeService?.measure();\n  }\n\n  /**\n   * Clear the entire buffer, making the prompt line the new first line.\n   */\n  public clear(): void {\n    if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n      // Don't clear if it's already clear\n      return;\n    }\n    this.buffer.clearAllMarkers();\n    this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);\n    this.buffer.lines.length = 1;\n    this.buffer.ydisp = 0;\n    this.buffer.ybase = 0;\n    this.buffer.y = 0;\n    for (let i = 1; i < this.rows; i++) {\n      this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n    }\n    // IMPORTANT: Fire scroll event before viewport is reset. This ensures embedders get the clear\n    // scroll event and that the viewport's state will be valid for immediate writes.\n    this._onScroll.fire({ position: this.buffer.ydisp });\n    this.refresh(0, this.rows - 1);\n  }\n\n  /**\n   * Reset terminal.\n   * Note: Calling this directly from JS is synchronous but does not clear\n   * input buffers and does not reset the parser, thus the terminal will\n   * continue to apply pending input data.\n   * If you need in band reset (synchronous with input data) consider\n   * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).\n   */\n  public reset(): void {\n    /**\n     * Since _setup handles a full terminal creation, we have to carry forward\n     * a few things that should not reset.\n     */\n    this.options.rows = this.rows;\n    this.options.cols = this.cols;\n    const customKeyEventHandler = this._customKeyEventHandler;\n\n    this._setup();\n    super.reset();\n    this._mouseService?.reset();\n    this._selectionService?.reset();\n    this._decorationService.reset();\n\n    // reattach\n    this._customKeyEventHandler = customKeyEventHandler;\n\n    // do a full screen refresh\n    this.refresh(0, this.rows - 1, true);\n  }\n\n  public clearTextureAtlas(): void {\n    this._renderService?.clearTextureAtlas();\n  }\n\n  private _reportFocus(): void {\n    if (this.element?.classList.contains('focus')) {\n      this.coreService.triggerDataEvent(C0.ESC + '[I');\n    } else {\n      this.coreService.triggerDataEvent(C0.ESC + '[O');\n    }\n  }\n\n  private _reportWindowsOptions(type: WindowsOptionsReportType): void {\n    if (!this._renderService) {\n      return;\n    }\n\n    switch (type) {\n      case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS:\n        const canvasWidth = this._renderService.dimensions.css.canvas.width.toFixed(0);\n        const canvasHeight = this._renderService.dimensions.css.canvas.height.toFixed(0);\n        this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);\n        break;\n      case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS:\n        const cellWidth = this._renderService.dimensions.css.cell.width.toFixed(0);\n        const cellHeight = this._renderService.dimensions.css.cell.height.toFixed(0);\n        this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);\n        break;\n    }\n  }\n\n}\n\n/**\n * Helpers\n */\n\nfunction wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean {\n  return ev.keyCode === 16 || // Shift\n    ev.keyCode === 17 || // Ctrl\n    ev.keyCode === 18 || // Alt\n    ev.keyCode === 91 || // Meta (Left)\n    ev.keyCode === 92 || // Meta (Right)\n    ev.keyCode === 93 || // Meta (Menu)\n    ev.keyCode === 224 || // Meta (Firefox)\n    ev.key === 'Meta';\n}\n"
  },
  {
    "path": "src/browser/Dom.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Minimal DOM helpers for xterm.js browser code.\n */\n\nimport { IntervalTimer } from 'common/Async';\nimport { IDisposable } from 'common/Lifecycle';\n\nexport function getWindow(e: Node | UIEvent | undefined | null): Window {\n  const candidateNode = e as Node | undefined | null;\n  if (candidateNode?.ownerDocument?.defaultView) {\n    return candidateNode.ownerDocument.defaultView;\n  }\n\n  const candidateEvent = e as UIEvent | undefined | null;\n  if (candidateEvent?.view) {\n    return candidateEvent.view;\n  }\n\n  return window;\n}\n\nclass DomListener implements IDisposable {\n  private _handler: ((e: any) => void) | null;\n  private _node: EventTarget | null;\n  private readonly _type: string;\n  private readonly _options: boolean | AddEventListenerOptions | undefined;\n\n  constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) {\n    this._node = node;\n    this._type = type;\n    this._handler = handler;\n    this._options = options;\n    node.addEventListener(type, handler, options);\n  }\n\n  public dispose(): void {\n    if (!this._node || !this._handler) {\n      return;\n    }\n    this._node.removeEventListener(this._type, this._handler, this._options);\n    this._node = null;\n    this._handler = null;\n  }\n}\n\nexport function addDisposableListener<K extends keyof GlobalEventHandlersEventMap>(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable;\nexport function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable;\nexport function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable;\nexport function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable {\n  return new DomListener(node, type, handler, useCaptureOrOptions);\n}\n\nexport function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {\n  return addDisposableListener(node, type, handler, useCapture);\n}\n\nexport const eventType = {\n  CLICK: 'click',\n  MOUSE_DOWN: 'mousedown',\n  MOUSE_OVER: 'mouseover',\n  MOUSE_LEAVE: 'mouseleave',\n  KEY_DOWN: 'keydown',\n  KEY_UP: 'keyup',\n  INPUT: 'input',\n  BLUR: 'blur',\n  FOCUS: 'focus',\n  CHANGE: 'change',\n  POINTER_DOWN: 'pointerdown',\n  POINTER_MOVE: 'pointermove',\n  POINTER_UP: 'pointerup',\n  MOUSE_WHEEL: 'wheel',\n  WHEEL: 'wheel'\n} as const;\n\nexport function getDomNodePagePosition(domNode: HTMLElement): { left: number, top: number, width: number, height: number } {\n  const bb = domNode.getBoundingClientRect();\n  const win = getWindow(domNode);\n  return {\n    left: bb.left + win.scrollX,\n    top: bb.top + win.scrollY,\n    width: bb.width,\n    height: bb.height\n  };\n}\n\nclass AnimationFrameQueueItem implements IDisposable {\n  private _canceled = false;\n\n  constructor(private readonly _runner: () => void, public priority: number) {\n  }\n\n  public dispose(): void {\n    this._canceled = true;\n  }\n\n  public execute(): void {\n    if (this._canceled) {\n      return;\n    }\n    try {\n      this._runner();\n    } catch (e) {\n      console.error(e);\n    }\n  }\n\n  public static sort(a: AnimationFrameQueueItem, b: AnimationFrameQueueItem): number {\n    return b.priority - a.priority;\n  }\n}\n\ninterface IWindowAnimationFrameState {\n  next: AnimationFrameQueueItem[];\n  current: AnimationFrameQueueItem[];\n  animFrameRequested: boolean;\n  inAnimationFrameRunner: boolean;\n}\n\nconst animationFrameState = new Map<Window, IWindowAnimationFrameState>();\n\nfunction getAnimationFrameState(targetWindow: Window): IWindowAnimationFrameState {\n  let state = animationFrameState.get(targetWindow);\n  if (!state) {\n    state = {\n      next: [],\n      current: [],\n      animFrameRequested: false,\n      inAnimationFrameRunner: false\n    };\n    animationFrameState.set(targetWindow, state);\n  }\n  return state;\n}\n\nfunction animationFrameRunner(targetWindow: Window): void {\n  const state = getAnimationFrameState(targetWindow);\n  state.animFrameRequested = false;\n\n  state.current = state.next;\n  state.next = [];\n\n  state.inAnimationFrameRunner = true;\n  while (state.current.length > 0) {\n    state.current.sort(AnimationFrameQueueItem.sort);\n    const top = state.current.shift()!;\n    top.execute();\n  }\n  state.inAnimationFrameRunner = false;\n}\n\nexport function scheduleAtNextAnimationFrame(targetWindow: Window, runner: () => void, priority: number = 0): IDisposable {\n  const state = getAnimationFrameState(targetWindow);\n  const item = new AnimationFrameQueueItem(runner, priority);\n  state.next.push(item);\n\n  if (!state.animFrameRequested) {\n    state.animFrameRequested = true;\n    targetWindow.requestAnimationFrame(() => animationFrameRunner(targetWindow));\n  }\n\n  return item;\n}\n\nexport class WindowIntervalTimer extends IntervalTimer {\n  private readonly _defaultTarget?: Window;\n\n  constructor(node?: Node) {\n    super();\n    this._defaultTarget = node ? getWindow(node) : undefined;\n  }\n\n  public cancelAndSet(runner: () => void, interval: number, targetWindow?: Window): void {\n    super.cancelAndSet(runner, interval, targetWindow ?? this._defaultTarget ?? window);\n  }\n}\n"
  },
  {
    "path": "src/browser/Linkifier.test.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { IBufferService } from 'common/services/Services';\nimport { Linkifier } from './Linkifier';\nimport { MockBufferService } from 'common/TestUtils.test';\nimport { ILink } from 'browser/Types';\nimport { LinkProviderService } from 'browser/services/LinkProviderService';\nimport jsdom = require('jsdom');\n\nclass TestLinkifier2 extends Linkifier {\n  public set currentLink(link: any) {\n    this._currentLink = link;\n  }\n\n  public linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {\n    this._linkHover(element, link, event);\n  }\n\n  public linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {\n    this._linkLeave(element, link, event);\n  }\n}\n\ndescribe('Linkifier2', () => {\n  let bufferService: IBufferService;\n  let linkifier: TestLinkifier2;\n\n  const link: ILink = {\n    text: 'foo',\n    range: {\n      start: {\n        x: 5,\n        y: 1\n      },\n      end: {\n        x: 7,\n        y: 1\n      }\n    },\n    activate: () => { }\n  };\n\n  beforeEach(() => {\n    const dom = new jsdom.JSDOM();\n    bufferService = new MockBufferService(100, 10);\n    linkifier = new TestLinkifier2(dom.window.document.createElement('div'), null!, null!, bufferService, new LinkProviderService());\n    linkifier.currentLink = {\n      link,\n      state: {\n        decorations: {\n          underline: true,\n          pointerCursor: true\n        },\n        isHovered: true\n      }\n    };\n  });\n\n  it('onShowLinkUnderline event range is correct', done => {\n    linkifier.onShowLinkUnderline(e => {\n      assert.equal(link.range.start.x - 1, e.x1);\n      assert.equal(link.range.start.y - 1, e.y1);\n      assert.equal(link.range.end.x, e.x2);\n      assert.equal(link.range.end.y - 1, e.y2);\n\n      done();\n    });\n\n    linkifier.linkHover({ classList: { add: () => { } } } as any, link, {} as any);\n  });\n\n  it('onHideLinkUnderline event range is correct', done => {\n    linkifier.onHideLinkUnderline(e => {\n      assert.equal(link.range.start.x - 1, e.x1);\n      assert.equal(link.range.start.y - 1, e.y1);\n      assert.equal(link.range.end.x, e.x2);\n      assert.equal(link.range.end.y - 1, e.y2);\n\n      done();\n    });\n\n    linkifier.linkLeave({ classList: { add: () => { } } } as any, link, {} as any);\n  });\n\n});\n"
  },
  {
    "path": "src/browser/Linkifier.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from 'browser/Types';\nimport { Disposable, dispose, toDisposable } from 'common/Lifecycle';\nimport { IDisposable } from 'common/Types';\nimport { IBufferService } from 'common/services/Services';\nimport { ILinkProviderService, IMouseCoordsService, IRenderService } from './services/Services';\nimport { Emitter } from 'common/Event';\nimport { addDisposableListener } from 'browser/Dom';\n\nexport class Linkifier extends Disposable implements ILinkifier2 {\n  public get currentLink(): ILinkWithState | undefined { return this._currentLink; }\n  protected _currentLink: ILinkWithState | undefined;\n  private _mouseDownLink: ILinkWithState | undefined;\n  private _lastMouseEvent: MouseEvent | undefined;\n  private _linkCacheDisposables: IDisposable[] = [];\n  private _lastBufferCell: IBufferCellPosition | undefined;\n  private _isMouseOut: boolean = true;\n  private _wasResized: boolean = false;\n  private _activeProviderReplies: Map<Number, ILinkWithState[] | undefined> | undefined;\n  private _activeLine: number = -1;\n\n  private readonly _onShowLinkUnderline = this._register(new Emitter<ILinkifierEvent>());\n  public readonly onShowLinkUnderline = this._onShowLinkUnderline.event;\n  private readonly _onHideLinkUnderline = this._register(new Emitter<ILinkifierEvent>());\n  public readonly onHideLinkUnderline = this._onHideLinkUnderline.event;\n\n  constructor(\n    private readonly _element: HTMLElement,\n    @IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,\n    @IRenderService private readonly _renderService: IRenderService,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @ILinkProviderService private readonly _linkProviderService: ILinkProviderService\n  ) {\n    super();\n    this._register(toDisposable(() => {\n      dispose(this._linkCacheDisposables);\n      this._linkCacheDisposables.length = 0;\n      this._lastMouseEvent = undefined;\n      // Clear out link providers as they could easily cause an embedder memory leak\n      this._activeProviderReplies?.clear();\n    }));\n    // Listen to resize to catch the case where it's resized and the cursor is out of the viewport.\n    this._register(this._bufferService.onResize(() => {\n      this._clearCurrentLink();\n      this._wasResized = true;\n    }));\n    this._register(addDisposableListener(this._element, 'mouseleave', () => {\n      this._isMouseOut = true;\n      this._clearCurrentLink();\n    }));\n    this._register(addDisposableListener(this._element, 'mousemove', this._handleMouseMove.bind(this)));\n    this._register(addDisposableListener(this._element, 'mousedown', this._handleMouseDown.bind(this)));\n    this._register(addDisposableListener(this._element, 'mouseup', this._handleMouseUp.bind(this)));\n  }\n\n  private _handleMouseMove(event: MouseEvent): void {\n    this._lastMouseEvent = event;\n\n    const position = this._positionFromMouseEvent(event, this._element);\n    if (!position) {\n      return;\n    }\n    this._isMouseOut = false;\n\n    // Ignore the event if it's an embedder created hover widget\n    const composedPath = event.composedPath() as HTMLElement[];\n    for (let i = 0; i < composedPath.length; i++) {\n      const target = composedPath[i];\n      // Hit Terminal.element, break and continue\n      if (target.classList.contains('xterm')) {\n        break;\n      }\n      // It's a hover, don't respect hover event\n      if (target.classList.contains('xterm-hover')) {\n        return;\n      }\n    }\n\n    if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) {\n      this._handleHover(position);\n      this._lastBufferCell = position;\n    }\n  }\n\n  private _handleHover(position: IBufferCellPosition): void {\n    // TODO: This currently does not cache link provider results across wrapped lines, activeLine\n    //       should be something like `activeRange: {startY, endY}`\n    // Check if we need to clear the link\n    if (this._activeLine !== position.y || this._wasResized) {\n      this._clearCurrentLink();\n      this._askForLink(position, false);\n      this._wasResized = false;\n      return;\n    }\n\n    // Check the if the link is in the mouse position\n    const isCurrentLinkInPosition = this._currentLink && this._linkAtPosition(this._currentLink.link, position);\n    if (!isCurrentLinkInPosition) {\n      this._clearCurrentLink();\n      this._askForLink(position, true);\n    }\n  }\n\n  private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void {\n    if (!this._activeProviderReplies || !useLineCache) {\n      this._activeProviderReplies?.forEach(reply => {\n        reply?.forEach(linkWithState => {\n          if (linkWithState.link.dispose) {\n            linkWithState.link.dispose();\n          }\n        });\n      });\n      this._activeProviderReplies = new Map();\n      this._activeLine = position.y;\n    }\n    let linkProvided = false;\n\n    // There is no link cached, so ask for one\n    for (const [i, linkProvider] of this._linkProviderService.linkProviders.entries()) {\n      if (useLineCache) {\n        const existingReply = this._activeProviderReplies?.get(i);\n        // If there isn't a reply, the provider hasn't responded yet.\n\n        // TODO: If there isn't a reply yet it means that the provider is still resolving. Ensuring\n        // provideLinks isn't triggered again saves ILink.hover firing twice though. This probably\n        // needs promises to get fixed\n        if (existingReply) {\n          linkProvided = this._checkLinkProviderResult(i, position, linkProvided);\n        }\n      } else {\n        linkProvider.provideLinks(position.y, (links: ILink[] | undefined) => {\n          if (this._isMouseOut) {\n            return;\n          }\n          const linksWithState: ILinkWithState[] | undefined = links?.map(link  => ({ link }));\n          this._activeProviderReplies?.set(i, linksWithState);\n          linkProvided = this._checkLinkProviderResult(i, position, linkProvided);\n\n          // If all providers have responded, remove lower priority links that intersect ranges of\n          // higher priority links\n          if (this._activeProviderReplies?.size === this._linkProviderService.linkProviders.length) {\n            this._removeIntersectingLinks(position.y, this._activeProviderReplies);\n          }\n        });\n      }\n    }\n  }\n\n  private _removeIntersectingLinks(y: number, replies: Map<Number, ILinkWithState[] | undefined>): void {\n    const occupiedCells = new Set<number>();\n    for (let i = 0; i < replies.size; i++) {\n      const providerReply = replies.get(i);\n      if (!providerReply) {\n        continue;\n      }\n      for (let i = 0; i < providerReply.length; i++) {\n        const linkWithState = providerReply[i];\n        const startX = linkWithState.link.range.start.y < y ? 0 : linkWithState.link.range.start.x;\n        const endX = linkWithState.link.range.end.y > y ? this._bufferService.cols : linkWithState.link.range.end.x;\n        for (let x = startX; x <= endX; x++) {\n          if (occupiedCells.has(x)) {\n            providerReply.splice(i--, 1);\n            break;\n          }\n          occupiedCells.add(x);\n        }\n      }\n    }\n  }\n\n  private _checkLinkProviderResult(index: number, position: IBufferCellPosition, linkProvided: boolean): boolean {\n    if (!this._activeProviderReplies) {\n      return linkProvided;\n    }\n\n    const links = this._activeProviderReplies.get(index);\n\n    // Check if every provider before this one has come back undefined\n    let hasLinkBefore = false;\n    for (let j = 0; j < index; j++) {\n      if (!this._activeProviderReplies.has(j) || this._activeProviderReplies.get(j)) {\n        hasLinkBefore = true;\n      }\n    }\n\n    // If all providers with higher priority came back undefined, then this provider's link for\n    // the position should be used\n    if (!hasLinkBefore && links) {\n      const linkAtPosition = links.find(link => this._linkAtPosition(link.link, position));\n      if (linkAtPosition) {\n        linkProvided = true;\n        this._handleNewLink(linkAtPosition);\n      }\n    }\n\n    // Check if all the providers have responded\n    if (this._activeProviderReplies.size === this._linkProviderService.linkProviders.length && !linkProvided) {\n      // Respect the order of the link providers\n      for (let j = 0; j < this._activeProviderReplies.size; j++) {\n        const currentLink = this._activeProviderReplies.get(j)?.find(link => this._linkAtPosition(link.link, position));\n        if (currentLink) {\n          linkProvided = true;\n          this._handleNewLink(currentLink);\n          break;\n        }\n      }\n    }\n\n    return linkProvided;\n  }\n\n  private _handleMouseDown(): void {\n    this._mouseDownLink = this._currentLink;\n  }\n\n  private _handleMouseUp(event: MouseEvent): void {\n    if (!this._currentLink) {\n      return;\n    }\n\n    const position = this._positionFromMouseEvent(event, this._element);\n    if (!position) {\n      return;\n    }\n\n    if (this._mouseDownLink && linkEquals(this._mouseDownLink.link, this._currentLink.link) && this._linkAtPosition(this._currentLink.link, position)) {\n      this._currentLink.link.activate(event, this._currentLink.link.text);\n    }\n  }\n\n  private _clearCurrentLink(startRow?: number, endRow?: number): void {\n    if (!this._currentLink || !this._lastMouseEvent) {\n      return;\n    }\n\n    // If we have a start and end row, check that the link is within it\n    if (!startRow || !endRow || (this._currentLink.link.range.start.y >= startRow && this._currentLink.link.range.end.y <= endRow)) {\n      this._linkLeave(this._element, this._currentLink.link, this._lastMouseEvent);\n      this._currentLink = undefined;\n      dispose(this._linkCacheDisposables);\n      this._linkCacheDisposables.length = 0;\n    }\n  }\n\n  private _handleNewLink(linkWithState: ILinkWithState): void {\n    if (!this._lastMouseEvent) {\n      return;\n    }\n\n    const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element);\n\n    if (!position) {\n      return;\n    }\n\n    // Trigger hover if the we have a link at the position\n    if (this._linkAtPosition(linkWithState.link, position)) {\n      this._currentLink = linkWithState;\n      this._currentLink.state = {\n        decorations: {\n          underline: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.underline,\n          pointerCursor: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.pointerCursor\n        },\n        isHovered: true\n      };\n      this._linkHover(this._element, linkWithState.link, this._lastMouseEvent);\n\n      // Add listener for tracking decorations changes\n      linkWithState.link.decorations = {} as ILinkDecorations;\n      Object.defineProperties(linkWithState.link.decorations, {\n        pointerCursor: {\n          get: () => this._currentLink?.state?.decorations.pointerCursor,\n          set: v => {\n            if (this._currentLink?.state && this._currentLink.state.decorations.pointerCursor !== v) {\n              this._currentLink.state.decorations.pointerCursor = v;\n              if (this._currentLink.state.isHovered) {\n                this._element.classList.toggle('xterm-cursor-pointer', v);\n              }\n            }\n          }\n        },\n        underline: {\n          get: () => this._currentLink?.state?.decorations.underline,\n          set: v => {\n            if (this._currentLink?.state && this._currentLink?.state?.decorations.underline !== v) {\n              this._currentLink.state.decorations.underline = v;\n              if (this._currentLink.state.isHovered) {\n                this._fireUnderlineEvent(linkWithState.link, v);\n              }\n            }\n          }\n        }\n      });\n\n      // Listen to viewport changes to re-render the link under the cursor (only when the line the\n      // link is on changes)\n      this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {\n        // Sanity check, this shouldn't happen in practice as this listener would be disposed\n        if (!this._currentLink) {\n          return;\n        }\n        // When start is 0 a scroll most likely occurred, make sure links above the fold also get\n        // cleared.\n        const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;\n        const end = this._bufferService.buffer.ydisp + 1 + e.end;\n        // Only clear the link if the viewport change happened on this line\n        if (this._currentLink.link.range.start.y >= start && this._currentLink.link.range.end.y <= end) {\n          this._clearCurrentLink(start, end);\n          if (this._lastMouseEvent) {\n            // re-eval previously active link after changes\n            const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element);\n            if (position) {\n              this._askForLink(position, false);\n            }\n          }\n        }\n      }));\n    }\n  }\n\n  protected _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {\n    if (this._currentLink?.state) {\n      this._currentLink.state.isHovered = true;\n      if (this._currentLink.state.decorations.underline) {\n        this._fireUnderlineEvent(link, true);\n      }\n      if (this._currentLink.state.decorations.pointerCursor) {\n        element.classList.add('xterm-cursor-pointer');\n      }\n    }\n\n    if (link.hover) {\n      link.hover(event, link.text);\n    }\n  }\n\n  private _fireUnderlineEvent(link: ILink, showEvent: boolean): void {\n    const range = link.range;\n    const scrollOffset = this._bufferService.buffer.ydisp;\n    const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined);\n    const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline;\n    emitter.fire(event);\n  }\n\n  protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {\n    if (this._currentLink?.state) {\n      this._currentLink.state.isHovered = false;\n      if (this._currentLink.state.decorations.underline) {\n        this._fireUnderlineEvent(link, false);\n      }\n      if (this._currentLink.state.decorations.pointerCursor) {\n        element.classList.remove('xterm-cursor-pointer');\n      }\n    }\n\n    if (link.leave) {\n      link.leave(event, link.text);\n    }\n  }\n\n  /**\n   * Check if the buffer position is within the link\n   * @param link\n   * @param position\n   */\n  private _linkAtPosition(link: ILink, position: IBufferCellPosition): boolean {\n    const lower = link.range.start.y * this._bufferService.cols + link.range.start.x;\n    const upper = link.range.end.y * this._bufferService.cols + link.range.end.x;\n    const current = position.y * this._bufferService.cols + position.x;\n    return (lower <= current && current <= upper);\n  }\n\n  /**\n   * Get the buffer position from a mouse event\n   * @param event\n   */\n  private _positionFromMouseEvent(event: MouseEvent, element: HTMLElement): IBufferCellPosition | undefined {\n    const coords = this._mouseCoordsService.getCoords(event, element, this._bufferService.cols, this._bufferService.rows);\n    if (!coords) {\n      return;\n    }\n\n    return { x: coords[0], y: coords[1] + this._bufferService.buffer.ydisp };\n  }\n\n  private _createLinkUnderlineEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {\n    return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };\n  }\n}\n\nfunction linkEquals(a: ILink, b: ILink): boolean {\n  return (\n    a.text === b.text &&\n    a.range.start.x === b.range.start.x &&\n    a.range.start.y === b.range.start.y &&\n    a.range.end.x === b.range.end.x &&\n    a.range.end.y === b.range.end.y\n  );\n}\n"
  },
  {
    "path": "src/browser/LocalizableStrings.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// This file contains strings that get exported in the API so they can be localized\n\nlet promptLabelInternal = 'Terminal input';\nconst promptLabel = {\n  get: () => promptLabelInternal,\n  set: (value: string) => promptLabelInternal = value\n};\n\nlet tooMuchOutputInternal = 'Too much output to announce, navigate to rows manually to read';\nconst tooMuchOutput = {\n  get: () => tooMuchOutputInternal,\n  set: (value: string) => tooMuchOutputInternal = value\n};\n\nexport {\n  promptLabel,\n  tooMuchOutput\n};\n"
  },
  {
    "path": "src/browser/OscLinkProvider.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferRange, ILink } from 'browser/Types';\nimport { ILinkProvider } from 'browser/services/Services';\nimport { CellData } from 'common/buffer/CellData';\nimport { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';\n\nexport class OscLinkProvider implements ILinkProvider {\n  private readonly _workCell = new CellData();\n\n  constructor(\n    @IBufferService private readonly _bufferService: IBufferService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @IOscLinkService private readonly _oscLinkService: IOscLinkService\n  ) {\n  }\n\n  public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {\n    const line = this._bufferService.buffer.lines.get(y - 1);\n    if (!line) {\n      callback(undefined);\n      return;\n    }\n\n    const result: ILink[] = [];\n    const linkHandler = this._optionsService.rawOptions.linkHandler;\n    const cell = this._workCell;\n    const lineLength = line.getTrimmedLength();\n    let currentLinkId = -1;\n    let currentStart = -1;\n    let finishLink = false;\n    for (let x = 0; x < lineLength; x++) {\n      // Minor optimization, only check for content if there isn't a link in case the link ends with\n      // a null cell\n      if (currentStart === -1 && !line.hasContent(x)) {\n        continue;\n      }\n\n      line.loadCell(x, cell);\n      if (cell.hasExtendedAttrs() && cell.extended.urlId) {\n        if (currentStart === -1) {\n          currentStart = x;\n          currentLinkId = cell.extended.urlId;\n          continue;\n        } else {\n          finishLink = cell.extended.urlId !== currentLinkId;\n        }\n      } else {\n        if (currentStart !== -1) {\n          finishLink = true;\n        }\n      }\n\n      if (finishLink || (currentStart !== -1 && x === lineLength - 1)) {\n        const text = this._oscLinkService.getLinkData(currentLinkId)?.uri;\n        if (text) {\n          // These ranges are 1-based\n          const range: IBufferRange = {\n            start: {\n              x: currentStart + 1,\n              y\n            },\n            end: {\n              // Offset end x if it's a link that ends on the last cell in the line\n              x: x + (!finishLink && x === lineLength - 1 ? 1 : 0),\n              y\n            }\n          };\n\n          let ignoreLink = false;\n          if (!linkHandler?.allowNonHttpProtocols) {\n            try {\n              const parsed = new URL(text);\n              if (!['http:', 'https:'].includes(parsed.protocol)) {\n                ignoreLink = true;\n              }\n            } catch {\n              // Ignore invalid URLs to prevent unexpected behaviors\n              ignoreLink = true;\n            }\n          }\n\n          if (!ignoreLink) {\n            // OSC links always use underline and pointer decorations\n            result.push({\n              text,\n              range,\n              activate: (e, text) => (linkHandler ? linkHandler.activate(e, text, range) : defaultActivate(e, text)),\n              hover: (e, text) => linkHandler?.hover?.(e, text, range),\n              leave: (e, text) => linkHandler?.leave?.(e, text, range)\n            });\n          }\n        }\n        finishLink = false;\n\n        // Clear link or start a new link if one starts immediately\n        if (cell.hasExtendedAttrs() && cell.extended.urlId) {\n          currentStart = x;\n          currentLinkId = cell.extended.urlId;\n        } else {\n          currentStart = -1;\n          currentLinkId = -1;\n        }\n      }\n    }\n\n    // TODO: Handle fetching and returning other link ranges to underline other links with the same\n    //       id\n    callback(result);\n  }\n}\n\nfunction defaultActivate(e: MouseEvent, uri: string): void {\n  const answer = confirm(`Do you want to navigate to ${uri}?\\n\\nWARNING: This link could potentially be dangerous`);\n  if (answer) {\n    const newWindow = window.open();\n    if (newWindow) {\n      try {\n        newWindow.opener = null;\n      } catch {\n        // no-op, Electron can throw\n      }\n      newWindow.location.href = uri;\n    } else {\n      console.warn('Opening link blocked as opener could not be cleared');\n    }\n  }\n}\n"
  },
  {
    "path": "src/browser/RenderDebouncer.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderDebouncerWithCallback } from 'browser/Types';\nimport { ICoreBrowserService } from 'browser/services/Services';\n\n/**\n * Debounces calls to render terminal rows using animation frames.\n */\nexport class RenderDebouncer implements IRenderDebouncerWithCallback {\n  private _rowStart: number | undefined;\n  private _rowEnd: number | undefined;\n  private _rowCount: number | undefined;\n  private _animationFrame: number | undefined;\n  private _refreshCallbacks: FrameRequestCallback[] = [];\n\n  constructor(\n    private _renderCallback: (start: number, end: number) => void,\n    private readonly _coreBrowserService: ICoreBrowserService\n  ) {\n  }\n\n  public dispose(): void {\n    if (this._animationFrame) {\n      this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);\n      this._animationFrame = undefined;\n    }\n  }\n\n  public addRefreshCallback(callback: FrameRequestCallback): number {\n    this._refreshCallbacks.push(callback);\n    if (!this._animationFrame) {\n      this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());\n    }\n    return this._animationFrame;\n  }\n\n  public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {\n    this._rowCount = rowCount;\n    // Get the min/max row start/end for the arg values\n    rowStart = rowStart ?? 0;\n    rowEnd = rowEnd ?? this._rowCount - 1;\n    // Set the properties to the updated values\n    this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;\n    this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;\n\n    if (this._animationFrame) {\n      return;\n    }\n\n    this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());\n  }\n\n  private _innerRefresh(): void {\n    this._animationFrame = undefined;\n\n    // Make sure values are set\n    if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {\n      this._runRefreshCallbacks();\n      return;\n    }\n\n    // Clamp values\n    const start = Math.max(this._rowStart, 0);\n    const end = Math.min(this._rowEnd, this._rowCount - 1);\n\n    // Reset debouncer (this happens before render callback as the render could trigger it again)\n    this._rowStart = undefined;\n    this._rowEnd = undefined;\n\n    // Run render callback\n    this._renderCallback(start, end);\n    this._runRefreshCallbacks();\n  }\n\n  private _runRefreshCallbacks(): void {\n    for (const callback of this._refreshCallbacks) {\n      callback(0);\n    }\n    this._refreshCallbacks = [];\n  }\n}\n"
  },
  {
    "path": "src/browser/Terminal.test.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { MockCompositionHelper, MockRenderer, MockViewport, TestTerminal } from 'browser/TestUtils.test';\nimport type { IBrowser } from 'browser/Types';\nimport { assert } from 'chai';\nimport { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { CellData } from 'common/buffer/CellData';\nimport { MockUnicodeService, createCellData } from 'common/TestUtils.test';\nimport { IMarker } from 'common/Types';\n\nconst INIT_COLS = 80;\nconst INIT_ROWS = 24;\n\n// grab wcwidth from mock unicode service (hardcoded to V6)\nconst wcwidth = (new MockUnicodeService()).wcwidth;\n\ndescribe('Terminal', () => {\n  let term: TestTerminal;\n  const termOptions = {\n    cols: INIT_COLS,\n    rows: INIT_ROWS\n  };\n\n  beforeEach(() => {\n    term = new TestTerminal(termOptions);\n    term.refresh = () => { };\n    (term as any).renderer = new MockRenderer();\n    (term as any).viewport = new MockViewport();\n    (term as any)._compositionHelper = new MockCompositionHelper();\n    (term as any).element = {\n      classList: {\n        toggle: () => { },\n        remove: () => { }\n      }\n    };\n  });\n\n  it('should not mutate the options parameter', () => {\n    term.options.cols = 1000;\n\n    assert.deepEqual(termOptions, {\n      cols: INIT_COLS,\n      rows: INIT_ROWS\n    });\n  });\n\n  describe('events', () => {\n    it('should fire the onData evnet', (done) => {\n      term.onData(() => done());\n      term.coreService.triggerDataEvent('fake');\n    });\n    it('should fire the onCursorMove event', () => {\n      return new Promise<void>(async r => {\n        term.onCursorMove(() => r());\n        await term.writeP('foo');\n      });\n    });\n    it('should fire the onLineFeed event', () => {\n      return new Promise<void>(async r => {\n        term.onLineFeed(() => r());\n        await term.writeP('\\n');\n      });\n    });\n    it('should fire a scroll event when scrollback is created', () => {\n      return new Promise<void>(async r => {\n        term.onScroll(() => r());\n        await term.writeP('\\n'.repeat(INIT_ROWS));\n      });\n    });\n    it('should fire a scroll event when scrollback is cleared', () => {\n      return new Promise<void>(async r => {\n        await term.writeP('\\n'.repeat(INIT_ROWS));\n        term.onScroll(() => r());\n        term.clear();\n      });\n    });\n    it('should fire a key event after a keypress DOM event', (done) => {\n      term.onKey(e => {\n        assert.equal(typeof e.key, 'string');\n        assert.equal(e.domEvent instanceof Object, true);\n        done();\n      });\n      const evKeyPress = {\n        preventDefault: () => { },\n        stopPropagation: () => { },\n        type: 'keypress',\n        keyCode: 13\n      } as KeyboardEvent;\n      term.keyPress(evKeyPress);\n    });\n    it('should fire a key event after a keydown DOM event', (done) => {\n      term.onKey(e => {\n        assert.equal(typeof e.key, 'string');\n        assert.equal(e.domEvent instanceof Object, true);\n        done();\n      });\n      (term as any).textarea = { value: '' };\n      const evKeyDown = {\n        preventDefault: () => { },\n        stopPropagation: () => { },\n        type: 'keydown',\n        keyCode: 13\n      } as KeyboardEvent;\n      term.keyDown(evKeyDown);\n    });\n    it('should fire the onResize event', (done) => {\n      term.onResize(e => {\n        assert.equal(typeof e.cols, 'number');\n        assert.equal(typeof e.rows, 'number');\n        done();\n      });\n      term.resize(1, 1);\n    });\n    it('should fire the onScroll event', (done) => {\n      term.onScroll(e => {\n        assert.equal(typeof e, 'number');\n        done();\n      });\n      term.scroll(DEFAULT_ATTR_DATA.clone());\n    });\n    it('should fire the onTitleChange event', (done) => {\n      term.onTitleChange(e => {\n        assert.equal(e, 'title');\n        done();\n      });\n      term.write('\\x1b]2;title\\x07');\n    });\n    it('should fire the onBell event', (done) => {\n      term.onBell(e => {\n        done();\n      });\n      term.write('\\x07');\n    });\n  });\n\n  describe('attachCustomKeyEventHandler', () => {\n    const evKeyDown = {\n      preventDefault: () => { },\n      stopPropagation: () => { },\n      type: 'keydown',\n      keyCode: 77\n    } as KeyboardEvent;\n    const evKeyPress = {\n      preventDefault: () => { },\n      stopPropagation: () => { },\n      type: 'keypress',\n      keyCode: 77\n    } as KeyboardEvent;\n\n    beforeEach(() => {\n      term.clearSelection = () => { };\n    });\n\n    it('should process the keydown/keypress event based on what the handler returns', () => {\n      assert.equal(term.keyDown(evKeyDown), true);\n      assert.equal(term.keyPress(evKeyPress), true);\n      term.attachCustomKeyEventHandler(ev => ev.keyCode === 77);\n      assert.equal(term.keyDown(evKeyDown), true);\n      assert.equal(term.keyPress(evKeyPress), true);\n      term.attachCustomKeyEventHandler(ev => ev.keyCode !== 77);\n      assert.equal(term.keyDown(evKeyDown), false);\n      assert.equal(term.keyPress(evKeyPress), false);\n    });\n\n    it('should alive after reset(ESC c Full Reset (RIS))', () => {\n      term.attachCustomKeyEventHandler(ev => ev.keyCode !== 77);\n      assert.equal(term.keyDown(evKeyDown), false);\n      assert.equal(term.keyPress(evKeyPress), false);\n      term.reset();\n      assert.equal(term.keyDown(evKeyDown), false);\n      assert.equal(term.keyPress(evKeyPress), false);\n    });\n  });\n\n  describe('clear', () => {\n    it('should clear a buffer equal to rows', () => {\n      const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);\n      term.clear();\n      assert.equal(term.buffer.y, 0);\n      assert.equal(term.buffer.ybase, 0);\n      assert.equal(term.buffer.ydisp, 0);\n      assert.equal(term.buffer.lines.length, term.rows);\n      assert.deepEqual(term.buffer.lines.get(0), promptLine);\n      for (let i = 1; i < term.rows; i++) {\n        assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n      }\n    });\n    it('should clear a buffer larger than rows', async () => {\n      // Fill the buffer with dummy rows\n      await term.writeP('test\\n'.repeat(term.rows * 2));\n\n      const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);\n      term.clear();\n      assert.equal(term.buffer.y, 0);\n      assert.equal(term.buffer.ybase, 0);\n      assert.equal(term.buffer.ydisp, 0);\n      assert.equal(term.buffer.lines.length, term.rows);\n      assert.deepEqual(term.buffer.lines.get(0), promptLine);\n      for (let i = 1; i < term.rows; i++) {\n        assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n      }\n    });\n    it('should not break the prompt when cleared twice', () => {\n      const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);\n      term.clear();\n      term.clear();\n      assert.equal(term.buffer.y, 0);\n      assert.equal(term.buffer.ybase, 0);\n      assert.equal(term.buffer.ydisp, 0);\n      assert.equal(term.buffer.lines.length, term.rows);\n      assert.deepEqual(term.buffer.lines.get(0), promptLine);\n      for (let i = 1; i < term.rows; i++) {\n        assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n      }\n    });\n  });\n\n  describe('paste', () => {\n    it('should fire data event', done => {\n      term.onData(e => {\n        assert.equal(e, 'foo');\n        done();\n      });\n      term.paste('foo');\n    });\n    it('should sanitize \\\\n chars', done => {\n      term.onData(e => {\n        assert.equal(e, '\\rfoo\\rbar\\r');\n        done();\n      });\n      term.paste('\\r\\nfoo\\nbar\\r');\n    });\n    it('should respect bracketed paste mode', () => {\n      return new Promise<void>(async r => {\n        term.onData(e => {\n          assert.equal(e, '\\x1b[200~foo\\x1b[201~');\n          r();\n        });\n        await term.writeP('\\x1b[?2004h');\n        term.paste('foo');\n      });\n    });\n  });\n\n  describe('scroll', () => {\n    describe('scrollLines', () => {\n      let startYDisp: number;\n      beforeEach(async () => {\n        for (let i = 0; i < INIT_ROWS * 2; i++) {\n          await term.writeP('test\\r\\n');\n        }\n        startYDisp = INIT_ROWS + 1;\n      });\n      it('should scroll a single line', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollLines(-1);\n        assert.equal(term.buffer.ydisp, startYDisp - 1);\n        term.scrollLines(1);\n        assert.equal(term.buffer.ydisp, startYDisp);\n      });\n      it('should scroll multiple lines', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollLines(-5);\n        assert.equal(term.buffer.ydisp, startYDisp - 5);\n        term.scrollLines(5);\n        assert.equal(term.buffer.ydisp, startYDisp);\n      });\n      it('should not scroll beyond the bounds of the buffer', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollLines(1);\n        assert.equal(term.buffer.ydisp, startYDisp);\n        for (let i = 0; i < startYDisp; i++) {\n          term.scrollLines(-1);\n        }\n        assert.equal(term.buffer.ydisp, 0);\n        term.scrollLines(-1);\n        assert.equal(term.buffer.ydisp, 0);\n      });\n    });\n\n    describe('scrollPages', () => {\n      let startYDisp: number;\n      beforeEach(async () => {\n        for (let i = 0; i < term.rows * 3; i++) {\n          await term.writeP('test\\r\\n');\n        }\n        startYDisp = (term.rows * 2) + 1;\n      });\n      it('should scroll a single page', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollPages(-1);\n        assert.equal(term.buffer.ydisp, startYDisp - (term.rows - 1));\n        term.scrollPages(1);\n        assert.equal(term.buffer.ydisp, startYDisp);\n      });\n      it('should scroll a multiple pages', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollPages(-2);\n        assert.equal(term.buffer.ydisp, startYDisp - (term.rows - 1) * 2);\n        term.scrollPages(2);\n        assert.equal(term.buffer.ydisp, startYDisp);\n      });\n    });\n\n    describe('scrollToTop', () => {\n      beforeEach(async () => {\n        for (let i = 0; i < term.rows * 3; i++) {\n          await term.writeP('test\\r\\n');\n        }\n      });\n      it('should scroll to the top', () => {\n        assert.notEqual(term.buffer.ydisp, 0);\n        term.scrollToTop();\n        assert.equal(term.buffer.ydisp, 0);\n      });\n    });\n\n    describe('scrollToBottom', () => {\n      let startYDisp: number;\n      beforeEach(async () => {\n        for (let i = 0; i < term.rows * 3; i++) {\n          await term.writeP('test\\r\\n');\n        }\n        startYDisp = (term.rows * 2) + 1;\n      });\n      it('should scroll to the bottom', () => {\n        term.scrollLines(-1);\n        term.scrollToBottom();\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollPages(-1);\n        term.scrollToBottom();\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollToTop();\n        term.scrollToBottom();\n        assert.equal(term.buffer.ydisp, startYDisp);\n      });\n    });\n\n    describe('scrollToLine', () => {\n      let startYDisp: number;\n      beforeEach(async () => {\n        for (let i = 0; i < term.rows * 3; i++) {\n          await term.writeP('test\\r\\n');\n        }\n        startYDisp = (term.rows * 2) + 1;\n      });\n      it('should scroll to requested line', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollToLine(0);\n        assert.equal(term.buffer.ydisp, 0);\n        term.scrollToLine(10);\n        assert.equal(term.buffer.ydisp, 10);\n        term.scrollToLine(startYDisp);\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollToLine(20);\n        assert.equal(term.buffer.ydisp, 20);\n      });\n      it('should not scroll beyond boundary lines', () => {\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollToLine(-1);\n        assert.equal(term.buffer.ydisp, 0);\n        term.scrollToLine(startYDisp + 1);\n        assert.equal(term.buffer.ydisp, startYDisp);\n      });\n    });\n\n    describe('keyPress', () => {\n      it('should scroll down, when a key is pressed and terminal is scrolled up', () => {\n        const event = {\n          type: 'keydown',\n          key: 'a',\n          keyCode: 65,\n          preventDefault: () => { },\n          stopPropagation: () => { }\n        } as KeyboardEvent;\n\n        term.buffer.ydisp = 0;\n        term.buffer.ybase = 40;\n        term.keyPress(event);\n\n        // Ensure that now the terminal is scrolled to bottom\n        assert.equal(term.buffer.ydisp, term.buffer.ybase);\n      });\n\n      it('should not scroll down, when a custom keydown handler prevents the event', async () => {\n        // Add some output to the terminal\n        await term.writeP('test\\r\\n'.repeat(term.rows * 3));\n        const startYDisp = (term.rows * 2) + 1;\n        term.attachCustomKeyEventHandler(() => {\n          return false;\n        });\n\n        assert.equal(term.buffer.ydisp, startYDisp);\n        term.scrollLines(-1);\n        assert.equal(term.buffer.ydisp, startYDisp - 1);\n        term.keyPress({ keyCode: 0 });\n        assert.equal(term.buffer.ydisp, startYDisp - 1);\n      });\n    });\n\n    describe('keyDown', () => {\n      it('should not scroll down on modifier-only input in win32 input mode', async () => {\n        term.options.vtExtensions = { win32InputMode: true };\n        term.coreService.decPrivateModes.win32InputMode = true;\n        (term as any).textarea = { value: '' };\n\n        await term.writeP('test\\r\\n'.repeat(term.rows * 3));\n        const startYDisp = term.buffer.ydisp;\n        term.scrollLines(-1);\n        const scrolledYDisp = term.buffer.ydisp;\n        assert.equal(scrolledYDisp, startYDisp - 1);\n\n        const evKeyDown = {\n          type: 'keydown',\n          key: 'Control',\n          keyCode: 17,\n          ctrlKey: true,\n          preventDefault: () => { },\n          stopPropagation: () => { }\n        } as KeyboardEvent;\n\n        const evKeyUp = {\n          type: 'keyup',\n          key: 'Control',\n          keyCode: 17,\n          preventDefault: () => { },\n          stopPropagation: () => { }\n        } as KeyboardEvent;\n\n        term.keyDown(evKeyDown);\n        assert.equal(term.buffer.ydisp, scrolledYDisp);\n        (term as any)._keyUp(evKeyUp);\n        assert.equal(term.buffer.ydisp, scrolledYDisp);\n      });\n    });\n\n    describe('scroll() function', () => {\n      describe('when scrollback > 0', () => {\n        it('should create a new line and scroll', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.y = INIT_ROWS - 1; // Move cursor to last line\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS + 1);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');\n          assert.equal(term.buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getChars(), 'b');\n          assert.equal(term.buffer.lines.get(INIT_ROWS)!.loadCell(0, new CellData()).getChars(), '');\n        });\n\n        it('should properly scroll inside a scroll region (scrollTop set)', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.y = INIT_ROWS - 1; // Move cursor to last line\n          term.buffer.scrollTop = 1;\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c');\n        });\n\n        it('should properly scroll inside a scroll region (scrollBottom set)', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0));\n          term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0));\n          term.buffer.y = 3;\n          term.buffer.scrollBottom = 3;\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS + 1);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a', '\\'a\\' should be pushed to the scrollback');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'b');\n          assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'c');\n          assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), 'd');\n          assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\\'s index');\n          assert.equal(term.buffer.lines.get(5)!.loadCell(0, new CellData()).getChars(), 'e');\n        });\n\n        it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0));\n          term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0));\n          term.buffer.y = INIT_ROWS - 1; // Move cursor to last line\n          term.buffer.scrollTop = 1;\n          term.buffer.scrollBottom = 3;\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c', '\\'b\\' should be removed from the buffer');\n          assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd');\n          assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\\'s index');\n          assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e');\n        });\n      });\n\n      describe('when scrollback === 0', () => {\n        beforeEach(() => {\n          term.optionsService.options.scrollback = 0;\n          assert.equal(term.buffer.lines.maxLength, INIT_ROWS);\n        });\n\n        it('should create a new line and shift everything up', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.y = INIT_ROWS - 1; // Move cursor to last line\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          // 'a' gets pushed out of buffer\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'b');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), '');\n          assert.equal(term.buffer.lines.get(INIT_ROWS - 2)!.loadCell(0, new CellData()).getChars(), 'c');\n          assert.equal(term.buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getChars(), '');\n        });\n\n        it('should properly scroll inside a scroll region (scrollTop set)', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.y = INIT_ROWS - 1; // Move cursor to last line\n          term.buffer.scrollTop = 1;\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c');\n        });\n\n        it('should properly scroll inside a scroll region (scrollBottom set)', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0));\n          term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0));\n          term.buffer.y = 3;\n          term.buffer.scrollBottom = 3;\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'b');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c');\n          assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd');\n          assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\\'s index');\n          assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e');\n        });\n\n        it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {\n          term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0));\n          term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0));\n          term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0));\n          term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0));\n          term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0));\n          term.buffer.y = INIT_ROWS - 1; // Move cursor to last line\n          term.buffer.scrollTop = 1;\n          term.buffer.scrollBottom = 3;\n          term.scroll(DEFAULT_ATTR_DATA.clone());\n          assert.equal(term.buffer.lines.length, INIT_ROWS);\n          assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');\n          assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c', '\\'b\\' should be removed from the buffer');\n          assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd');\n          assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\\'s index');\n          assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e');\n        });\n      });\n    });\n  });\n\n  describe('Third level shift', () => {\n    let evKeyDown: any;\n    let evKeyPress: any;\n\n    beforeEach(() => {\n      term.clearSelection = () => { };\n      // term.compositionHelper = {\n      //   isComposing: false,\n      //   keydown: {\n      //     bind: () => {\n      //       return () => { return true; };\n      //     }\n      //   }\n      // };\n      evKeyDown = {\n        preventDefault: () => { },\n        stopPropagation: () => { },\n        type: 'keydown',\n        altKey: null,\n        keyCode: null\n      };\n      evKeyPress = {\n        preventDefault: () => { },\n        stopPropagation: () => { },\n        type: 'keypress',\n        altKey: null,\n        charCode: null,\n        keyCode: null\n      };\n    });\n\n    describe('with macOptionIsMeta', () => {\n      beforeEach(() => {\n        term.options.macOptionIsMeta = true;\n      });\n\n      it('should interfere with the alt key on keyDown', () => {\n        evKeyDown.altKey = true;\n        evKeyDown.keyCode = 81;\n        assert.equal(term.keyDown(evKeyDown), false);\n        evKeyDown.altKey = true;\n        evKeyDown.keyCode = 192;\n        assert.equal(term.keyDown(evKeyDown), false);\n      });\n    });\n\n    describe('On Mac OS', () => {\n      let originalBrowser: IBrowser;\n      beforeEach(() => {\n        originalBrowser = term.browser;\n        term.browser = { ...originalBrowser, isMac: true };\n      });\n      afterEach(() => term.browser = originalBrowser);\n\n      it('should not interfere with the alt key on keyDown', () => {\n        evKeyDown.altKey = true;\n        evKeyDown.keyCode = 81;\n        assert.equal(term.keyDown(evKeyDown), true);\n        evKeyDown.altKey = true;\n        evKeyDown.keyCode = 192;\n        term.keyDown(evKeyDown);\n        assert.equal(term.keyDown(evKeyDown), true);\n      });\n\n      it('should interfere with the alt + arrow keys', () => {\n        evKeyDown.altKey = true;\n        evKeyDown.keyCode = 37;\n        assert.equal(term.keyDown(evKeyDown), false);\n        evKeyDown.altKey = true;\n        evKeyDown.keyCode = 39;\n        assert.equal(term.keyDown(evKeyDown), false);\n      });\n\n      it('should emit key with alt + key on keyPress', (done) => {\n        const keys = ['@', '@', '\\\\', '\\\\', '|', '|'];\n\n        term.onKey(e => {\n          if (e.key) {\n            const index = keys.indexOf(e.key);\n            assert(index !== -1, 'Emitted wrong key: ' + e.key);\n            keys.splice(index, 1);\n          }\n          if (keys.length === 0) done();\n        });\n\n        evKeyPress.altKey = true;\n        // @\n        evKeyPress.charCode = null;\n        evKeyPress.keyCode = 64;\n        term.keyPress(evKeyPress);\n        // Firefox @\n        evKeyPress.charCode = 64;\n        evKeyPress.keyCode = 0;\n        term.keyPress(evKeyPress);\n        // \\\n        evKeyPress.charCode = null;\n        evKeyPress.keyCode = 92;\n        term.keyPress(evKeyPress);\n        // Firefox \\\n        evKeyPress.charCode = 92;\n        evKeyPress.keyCode = 0;\n        term.keyPress(evKeyPress);\n        // |\n        evKeyPress.charCode = null;\n        evKeyPress.keyCode = 124;\n        term.keyPress(evKeyPress);\n        // Firefox |\n        evKeyPress.charCode = 124;\n        evKeyPress.keyCode = 0;\n        term.keyPress(evKeyPress);\n      });\n    });\n\n    describe('On MS Windows', () => {\n      let originalBrowser: IBrowser;\n      beforeEach(() => {\n        originalBrowser = term.browser;\n        term.browser = { ...originalBrowser, isWindows: true };\n      });\n      afterEach(() => term.browser = originalBrowser);\n\n      it('should not interfere with the alt + ctrl key on keyDown', () => {\n        evKeyPress.altKey = true;\n        evKeyPress.ctrlKey = true;\n        evKeyPress.keyCode = 81;\n        assert.equal(term.keyDown(evKeyPress), true);\n        evKeyDown.altKey = true;\n        evKeyDown.ctrlKey = true;\n        evKeyDown.keyCode = 81;\n        term.keyDown(evKeyDown);\n        assert.equal(term.keyDown(evKeyPress), true);\n      });\n\n      it('should interfere with the alt + ctrl + arrow keys', () => {\n        evKeyDown.altKey = true;\n        evKeyDown.ctrlKey = true;\n\n        evKeyDown.keyCode = 37;\n        assert.equal(term.keyDown(evKeyDown), false);\n        evKeyDown.keyCode = 39;\n        term.keyDown(evKeyDown);\n        assert.equal(term.keyDown(evKeyDown), false);\n      });\n\n      it('should emit key with alt + ctrl + key on keyPress', (done) => {\n        const keys = ['@', '@', '\\\\', '\\\\', '|', '|'];\n\n        term.onKey(e => {\n          if (e.key) {\n            const index = keys.indexOf(e.key);\n            assert(index !== -1, 'Emitted wrong key: ' + e.key);\n            keys.splice(index, 1);\n          }\n          if (keys.length === 0) done();\n        });\n\n        evKeyPress.altKey = true;\n        evKeyPress.ctrlKey = true;\n\n        // @\n        evKeyPress.charCode = null;\n        evKeyPress.keyCode = 64;\n        term.keyPress(evKeyPress);\n        // Firefox @\n        evKeyPress.charCode = 64;\n        evKeyPress.keyCode = 0;\n        term.keyPress(evKeyPress);\n        // \\\n        evKeyPress.charCode = null;\n        evKeyPress.keyCode = 92;\n        term.keyPress(evKeyPress);\n        // Firefox \\\n        evKeyPress.charCode = 92;\n        evKeyPress.keyCode = 0;\n        term.keyPress(evKeyPress);\n        // |\n        evKeyPress.charCode = null;\n        evKeyPress.keyCode = 124;\n        term.keyPress(evKeyPress);\n        // Firefox |\n        evKeyPress.charCode = 124;\n        evKeyPress.keyCode = 0;\n        term.keyPress(evKeyPress);\n      });\n    });\n  });\n\n  describe('unicode - surrogates', () => {\n    for (let i = 0xDC00; i <= 0xDCF0; i += 0x10) {\n      const range = `0x${i.toString(16).toUpperCase()}-0x${(i + 0xF).toString(16).toUpperCase()}`;\n      it(`${range}: 2 characters per cell`, async function (): Promise<void> {\n        const high = String.fromCharCode(0xD800);\n        const cell = new CellData();\n        const values: string[] = [];\n        for (let j = i; j <= i + 0xF; j++) {\n          values.push(high + String.fromCharCode(j));\n        }\n        await term.writeP(values.join('\\r\\n'));\n        for (let idx = 0; idx < values.length; idx++) {\n          const expected = values[idx];\n          const tchar = term.buffer.lines.get(idx)!.loadCell(0, cell);\n          assert.equal(tchar.getChars(), expected);\n          assert.equal(tchar.getChars().length, 2);\n          assert.equal(tchar.getWidth(), 1);\n          assert.equal(term.buffer.lines.get(idx)!.loadCell(1, cell).getChars(), '');\n        }\n      });\n      it(`${range}: 2 characters at last cell`, async () => {\n        const high = String.fromCharCode(0xD800);\n        const cell = new CellData();\n        const values: string[] = [];\n        for (let j = i; j <= i + 0xF; j++) {\n          values.push(high + String.fromCharCode(j));\n        }\n        await term.writeP(values.map((value, idx) => `\\x1b[${idx + 1};${term.cols}H${value}`).join(''));\n        for (let idx = 0; idx < values.length; idx++) {\n          const expected = values[idx];\n          assert.equal(term.buffer.lines.get(idx)!.loadCell(term.cols - 1, cell).getChars(), expected);\n          assert.equal(term.buffer.lines.get(idx)!.loadCell(term.cols - 1, cell).getChars().length, 2);\n          assert.equal(term.buffer.lines.get(idx + 1)!.loadCell(0, cell).getChars(), '');\n        }\n      });\n      it(`${range}: 2 characters per cell over line end with autowrap`, async function (): Promise<void> {\n        const high = String.fromCharCode(0xD800);\n        const cell = new CellData();\n        term.resize(term.cols, 40);\n        const values: string[] = [];\n        for (let j = i; j <= i + 0xF; j++) {\n          values.push(high + String.fromCharCode(j));\n        }\n        await term.writeP(values.map((value, idx) => `\\x1b[${idx * 2 + 1};${term.cols}H` + 'a' + value).join(''));\n        for (let idx = 0; idx < values.length; idx++) {\n          const expected = values[idx];\n          const row = idx * 2;\n          assert.equal(term.buffer.lines.get(row)!.loadCell(term.cols - 1, cell).getChars(), 'a');\n          assert.equal(term.buffer.lines.get(row + 1)!.loadCell(0, cell).getChars(), expected);\n          assert.equal(term.buffer.lines.get(row + 1)!.loadCell(0, cell).getChars().length, 2);\n          assert.equal(term.buffer.lines.get(row + 1)!.loadCell(1, cell).getChars(), '');\n        }\n      });\n      it(`${range}: 2 characters per cell over line end without autowrap`, async function (): Promise<void> {\n        const high = String.fromCharCode(0xD800);\n        const cell = new CellData();\n        const values: string[] = [];\n        for (let j = i; j <= i + 0xF; j++) {\n          const width = wcwidth((0xD800 - 0xD800) * 0x400 + j - 0xDC00 + 0x10000);\n          if (width !== 1) {\n            continue;\n          }\n          values.push(high + String.fromCharCode(j));\n        }\n        await term.writeP('\\x1b[?7l' + values.map((value, idx) => `\\x1b[${idx + 1};${term.cols}H` + 'a' + value).join(''));\n        for (let idx = 0; idx < values.length; idx++) {\n          const expected = values[idx];\n          assert.equal(term.buffer.lines.get(idx)!.loadCell(term.cols - 1, cell).getChars(), expected);\n          assert.equal(term.buffer.lines.get(idx)!.loadCell(term.cols - 1, cell).getChars().length, 2);\n          assert.equal(term.buffer.lines.get(idx + 1)!.loadCell(1, cell).getChars(), '');\n        }\n      });\n      it(`${range}: splitted surrogates`, async function (): Promise<void> {\n        const high = String.fromCharCode(0xD800);\n        const cell = new CellData();\n        const values: string[] = [];\n        for (let j = i; j <= i + 0xF; j++) {\n          values.push(high + String.fromCharCode(j));\n        }\n        await term.writeP(values.join('\\r\\n'));\n        for (let idx = 0; idx < values.length; idx++) {\n          const expected = values[idx];\n          const tchar = term.buffer.lines.get(idx)!.loadCell(0, cell);\n          assert.equal(tchar.getChars(), expected);\n          assert.equal(tchar.getChars().length, 2);\n          assert.equal(tchar.getWidth(), 1);\n          assert.equal(term.buffer.lines.get(idx)!.loadCell(1, cell).getChars(), '');\n        }\n      });\n    }\n  });\n\n  describe('unicode - combining characters', () => {\n    const cell = new CellData();\n    it('café', async () => {\n      await term.writeP('cafe\\u0301');\n      term.buffer.lines.get(0)!.loadCell(3, cell);\n      assert.equal(cell.getChars(), 'e\\u0301');\n      assert.equal(cell.getChars().length, 2);\n      assert.equal(cell.getWidth(), 1);\n    });\n    it('café - end of line', async () => {\n      term.buffer.x = term.cols - 1 - 3;\n      await term.writeP('cafe\\u0301');\n      term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);\n      assert.equal(cell.getChars(), 'e\\u0301');\n      assert.equal(cell.getChars().length, 2);\n      assert.equal(cell.getWidth(), 1);\n      term.buffer.lines.get(0)!.loadCell(1, cell);\n      assert.equal(cell.getChars(), '');\n      assert.equal(cell.getChars().length, 0);\n      assert.equal(cell.getWidth(), 1);\n    });\n    it('multiple combined é', async () => {\n      await term.writeP('e\\u0301'.repeat(99));\n      for (let i = 0; i < term.cols; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        assert.equal(cell.getChars(), 'e\\u0301');\n        assert.equal(cell.getChars().length, 2);\n        assert.equal(cell.getWidth(), 1);\n      }\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), 'e\\u0301');\n      assert.equal(cell.getChars().length, 2);\n      assert.equal(cell.getWidth(), 1);\n    });\n    it('multiple surrogate with combined', async () => {\n      await term.writeP('\\uD800\\uDC00\\u0301'.repeat(99));\n      for (let i = 0; i < term.cols; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        assert.equal(cell.getChars(), '\\uD800\\uDC00\\u0301');\n        assert.equal(cell.getChars().length, 3);\n        assert.equal(cell.getWidth(), 1);\n      }\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '\\uD800\\uDC00\\u0301');\n      assert.equal(cell.getChars().length, 3);\n      assert.equal(cell.getWidth(), 1);\n    });\n  });\n\n  describe('unicode - fullwidth characters', () => {\n    const cell = new CellData();\n    it('cursor movement even', async () => {\n      assert.equal(term.buffer.x, 0);\n      await term.writeP('￥');\n      assert.equal(term.buffer.x, 2);\n    });\n    it('cursor movement odd', async () => {\n      term.buffer.x = 1;\n      assert.equal(term.buffer.x, 1);\n      await term.writeP('￥');\n      assert.equal(term.buffer.x, 3);\n    });\n    it('line of ￥ even', async () => {\n      await term.writeP('￥'.repeat(49));\n      for (let i = 0; i < term.cols; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        if (i % 2) {\n          assert.equal(cell.getChars(), '');\n          assert.equal(cell.getChars().length, 0);\n          assert.equal(cell.getWidth(), 0);\n        } else {\n          assert.equal(cell.getChars(), '￥');\n          assert.equal(cell.getChars().length, 1);\n          assert.equal(cell.getWidth(), 2);\n        }\n      }\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '￥');\n      assert.equal(cell.getChars().length, 1);\n      assert.equal(cell.getWidth(), 2);\n    });\n    it('line of ￥ odd', async () => {\n      term.buffer.x = 1;\n      await term.writeP('￥'.repeat(49));\n      for (let i = 1; i < term.cols - 1; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        if (!(i % 2)) {\n          assert.equal(cell.getChars(), '');\n          assert.equal(cell.getChars().length, 0);\n          assert.equal(cell.getWidth(), 0);\n        } else {\n          assert.equal(cell.getChars(), '￥');\n          assert.equal(cell.getChars().length, 1);\n          assert.equal(cell.getWidth(), 2);\n        }\n      }\n      term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);\n      assert.equal(cell.getChars(), '');\n      assert.equal(cell.getChars().length, 0);\n      assert.equal(cell.getWidth(), 1);\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '￥');\n      assert.equal(cell.getChars().length, 1);\n      assert.equal(cell.getWidth(), 2);\n    });\n    it('line of ￥ with combining odd', async () => {\n      term.buffer.x = 1;\n      await term.writeP('￥\\u0301'.repeat(49));\n      for (let i = 1; i < term.cols - 1; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        if (!(i % 2)) {\n          assert.equal(cell.getChars(), '');\n          assert.equal(cell.getChars().length, 0);\n          assert.equal(cell.getWidth(), 0);\n        } else {\n          assert.equal(cell.getChars(), '￥\\u0301');\n          assert.equal(cell.getChars().length, 2);\n          assert.equal(cell.getWidth(), 2);\n        }\n      }\n      term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);\n      assert.equal(cell.getChars(), '');\n      assert.equal(cell.getChars().length, 0);\n      assert.equal(cell.getWidth(), 1);\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '￥\\u0301');\n      assert.equal(cell.getChars().length, 2);\n      assert.equal(cell.getWidth(), 2);\n    });\n    it('line of ￥ with combining even', async () => {\n      await term.writeP('￥\\u0301'.repeat(49));\n      for (let i = 0; i < term.cols; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        if (i % 2) {\n          assert.equal(cell.getChars(), '');\n          assert.equal(cell.getChars().length, 0);\n          assert.equal(cell.getWidth(), 0);\n        } else {\n          assert.equal(cell.getChars(), '￥\\u0301');\n          assert.equal(cell.getChars().length, 2);\n          assert.equal(cell.getWidth(), 2);\n        }\n      }\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '￥\\u0301');\n      assert.equal(cell.getChars().length, 2);\n      assert.equal(cell.getWidth(), 2);\n    });\n    it('line of surrogate fullwidth with combining odd', async () => {\n      term.buffer.x = 1;\n      await term.writeP('\\ud843\\ude6d\\u0301'.repeat(49));\n      for (let i = 1; i < term.cols - 1; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        if (!(i % 2)) {\n          assert.equal(cell.getChars(), '');\n          assert.equal(cell.getChars().length, 0);\n          assert.equal(cell.getWidth(), 0);\n        } else {\n          assert.equal(cell.getChars(), '\\ud843\\ude6d\\u0301');\n          assert.equal(cell.getChars().length, 3);\n          assert.equal(cell.getWidth(), 2);\n        }\n      }\n      term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);\n      assert.equal(cell.getChars(), '');\n      assert.equal(cell.getChars().length, 0);\n      assert.equal(cell.getWidth(), 1);\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '\\ud843\\ude6d\\u0301');\n      assert.equal(cell.getChars().length, 3);\n      assert.equal(cell.getWidth(), 2);\n    });\n    it('line of surrogate fullwidth with combining even', async () => {\n      await term.writeP('\\ud843\\ude6d\\u0301'.repeat(49));\n      for (let i = 0; i < term.cols; ++i) {\n        term.buffer.lines.get(0)!.loadCell(i, cell);\n        if (i % 2) {\n          assert.equal(cell.getChars(), '');\n          assert.equal(cell.getChars().length, 0);\n          assert.equal(cell.getWidth(), 0);\n        } else {\n          assert.equal(cell.getChars(), '\\ud843\\ude6d\\u0301');\n          assert.equal(cell.getChars().length, 3);\n          assert.equal(cell.getWidth(), 2);\n        }\n      }\n      term.buffer.lines.get(1)!.loadCell(0, cell);\n      assert.equal(cell.getChars(), '\\ud843\\ude6d\\u0301');\n      assert.equal(cell.getChars().length, 3);\n      assert.equal(cell.getWidth(), 2);\n    });\n  });\n\n  describe('insert mode', () => {\n    const cell = new CellData();\n    it('halfwidth - all', async () => {\n      await term.writeP('0123456789'.repeat(8).slice(-80));\n      term.buffer.x = 10;\n      term.buffer.y = 0;\n      term.write('\\x1b[4h');\n      await term.writeP('abcde');\n      assert.equal(term.buffer.lines.get(0)!.length, term.cols);\n      assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), 'a');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(14, cell).getChars(), 'e');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(15, cell).getChars(), '0');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '4');\n    });\n    it('fullwidth - insert', async () => {\n      await term.writeP('0123456789'.repeat(8).slice(-80));\n      term.buffer.x = 10;\n      term.buffer.y = 0;\n      term.write('\\x1b[4h');\n      await term.writeP('￥￥￥');\n      assert.equal(term.buffer.lines.get(0)!.length, term.cols);\n      assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), '￥');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), '');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(14, cell).getChars(), '￥');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(15, cell).getChars(), '');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '3');\n    });\n    it('fullwidth - right border', async () => {\n      await term.writeP('￥'.repeat(40));\n      term.buffer.x = 10;\n      term.buffer.y = 0;\n      term.write('\\x1b[4h');\n      await term.writeP('a');\n      assert.equal(term.buffer.lines.get(0)!.length, term.cols);\n      assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), 'a');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), '￥');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '');  // fullwidth char got replaced\n      await term.writeP('b');\n      assert.equal(term.buffer.lines.get(0)!.length, term.cols);\n      assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), 'b');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(12, cell).getChars(), '￥');\n      assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '');  // empty cell after fullwidth\n    });\n  });\n\n  describe('Windows Pty', () => {\n    it('should mark lines as wrapped when the line ends in a non-null character after a LF', async () => {\n      const data = [\n        'aaaaaaaaaa\\n\\r', // cannot wrap as it's the first\n        'aaaaaaaaa\\n\\r',  // wrapped (windows mode only)\n        'aaaaaaaaa'       // not wrapped\n      ];\n\n      const normalTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: {} });\n      await normalTerminal.writeP(data.join(''));\n      assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false);\n      assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false);\n      assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false);\n\n      const windowsModeTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: { backend: 'conpty', buildNumber: 19000 } });\n      await windowsModeTerminal.writeP(data.join(''));\n      assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false);\n      assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');\n      assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false);\n    });\n\n    it('should mark lines as wrapped when the line ends in a non-null character after a CUP', async () => {\n      const data = [\n        'aaaaaaaaaa\\x1b[2;1H', // cannot wrap as it's the first\n        'aaaaaaaaa\\x1b[3;1H',  // wrapped (windows mode only)\n        'aaaaaaaaa'             // not wrapped\n      ];\n\n      const normalTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: {} });\n      await normalTerminal.writeP(data.join(''));\n      assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false);\n      assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false);\n      assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false);\n\n      const windowsModeTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: { backend: 'conpty', buildNumber: 19000 } });\n      await windowsModeTerminal.writeP(data.join(''));\n      assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false);\n      assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');\n      assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false);\n    });\n  });\n  it('convertEol setting', async () => {\n    // not converting\n    const termNotConverting = new TestTerminal({ cols: 15, rows: 10 });\n    await termNotConverting.writeP('Hello\\nWorld');\n    assert.equal(termNotConverting.buffer.lines.get(0)!.translateToString(false), 'Hello          ');\n    assert.equal(termNotConverting.buffer.lines.get(1)!.translateToString(false), '     World     ');\n    assert.equal(termNotConverting.buffer.lines.get(0)!.translateToString(true), 'Hello');\n    assert.equal(termNotConverting.buffer.lines.get(1)!.translateToString(true), '     World');\n\n    // converting\n    const termConverting = new TestTerminal({ cols: 15, rows: 10, convertEol: true });\n    await termConverting.writeP('Hello\\nWorld');\n    assert.equal(termConverting.buffer.lines.get(0)!.translateToString(false), 'Hello          ');\n    assert.equal(termConverting.buffer.lines.get(1)!.translateToString(false), 'World          ');\n    assert.equal(termConverting.buffer.lines.get(0)!.translateToString(true), 'Hello');\n    assert.equal(termConverting.buffer.lines.get(1)!.translateToString(true), 'World');\n  });\n\n  // FIXME: move to common/CoreTerminal.test once the trimming is moved over\n  describe('marker lifecycle', () => {\n    // create a 10x5 terminal with markers on every line\n    // to test marker lifecycle under various terminal actions\n    let markers: IMarker[];\n    let disposeStack: IMarker[];\n    let term: TestTerminal;\n    beforeEach(async () => {\n      term = new TestTerminal({});\n      markers = [];\n      disposeStack = [];\n      term.optionsService.options.scrollback = 1;\n      term.resize(10, 5);\n      markers.push(term.buffers.active.addMarker(term.buffers.active.y));\n      await term.writeP('\\x1b[r0\\r\\n');\n      markers.push(term.buffers.active.addMarker(term.buffers.active.y));\n      await term.writeP('1\\r\\n');\n      markers.push(term.buffers.active.addMarker(term.buffers.active.y));\n      await term.writeP('2\\r\\n');\n      markers.push(term.buffers.active.addMarker(term.buffers.active.y));\n      await term.writeP('3\\r\\n');\n      markers.push(term.buffers.active.addMarker(term.buffers.active.y));\n      await term.writeP('4');\n      for (let i = 0; i < markers.length; ++i) {\n        const marker = markers[i];\n        marker.onDispose(() => disposeStack.push(marker));\n      }\n    });\n    it('initial', () => {\n      assert.deepEqual(markers.map(m => m.line), [0, 1, 2, 3, 4]);\n    });\n    it('should dispose on normal trim off the top', async () => {\n      // moves top line into scrollback\n      await term.writeP('\\n');\n      assert.deepEqual(disposeStack, []);\n      // trims first marker\n      await term.writeP('\\n');\n      assert.deepEqual(disposeStack, [markers[0]]);\n      // trims second marker\n      await term.writeP('\\n');\n      assert.deepEqual(disposeStack, [markers[0], markers[1]]);\n      // trimmed marker objs should be disposed\n      assert.deepEqual(disposeStack.map(el => el.isDisposed), [true, true]);\n      // trimmed markers should contain line -1\n      assert.deepEqual(disposeStack.map(el => el.line), [-1, -1]);\n    });\n    it('should dispose on DL', async () => {\n      await term.writeP('\\x1b[3;1H');  // move cursor to 0, 2\n      await term.writeP('\\x1b[2M');    // delete 2 lines\n      assert.deepEqual(disposeStack, [markers[2], markers[3]]);\n    });\n    it('should dispose on IL', async () => {\n      await term.writeP('\\x1b[3;1H');  // move cursor to 0, 2\n      await term.writeP('\\x1b[2L');    // insert 2 lines\n      assert.deepEqual(disposeStack, [markers[4], markers[3]]);\n      assert.deepEqual(markers.map(el => el.line), [0, 1, 4, -1, -1]);\n    });\n    it('should dispose on resize', () => {\n      term.resize(10, 2);\n      assert.deepEqual(disposeStack, [markers[0], markers[1]]);\n      assert.deepEqual(markers.map(el => el.line), [-1, -1, 0, 1, 2]);\n    });\n  });\n\n  describe('options', () => {\n    beforeEach(async () => {\n      term = new TestTerminal({});\n    });\n    it('get options', () => {\n      assert.equal(term.options.cols, 80);\n      assert.equal(term.options.rows, 24);\n    });\n    it('set options', async () => {\n      term.options.cols = 40;\n      assert.equal(term.options.cols, 40);\n      term.options.rows = 20;\n      assert.equal(term.options.rows, 20);\n    });\n  });\n});\n"
  },
  {
    "path": "src/browser/Terminal2.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs';\nimport * as pty from 'node-pty';\nimport { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';\nimport { IDisposable } from '@xterm/xterm';\n\n// all test files expect terminal in 80x25\nconst COLS = 80;\nconst ROWS = 25;\n\nconst escapeSequenceFilesDir = path.join(__dirname, '../../fixtures/escape_sequence_files');\nconst TESTFILES = fs.readdirSync(escapeSequenceFilesDir)\n  .filter(f => f.endsWith('.in'))\n  .map(f => path.join(escapeSequenceFilesDir, f));\nconst SKIP_FILES = [\n  't0055-EL.in',            // EL/ED handle cursor at cols differently (see #3362)\n  't0084-CBT.in',\n  't0101-NLM.in',\n  't0103-reverse_wrap.in',  // not comparable, we deviate from xterm reverse wrap on purpose\n  't0504-vim.in'\n];\nif (os.platform() === 'darwin') {\n  // These are failing on macOS only (termios related?)\n  SKIP_FILES.push(\n    't0003-line_wrap.in',\n    't0005-CR.in',\n    't0009-NEL.in',\n    't0503-zsh_ls_color.in'\n  );\n}\n// filter skipFilenames\nconst FILES = TESTFILES.filter(value => !SKIP_FILES.includes(path.basename(value)));\n\ndescribe('Escape Sequence Files', function(): void {\n  this.timeout(1000);\n\n  let ptyTerm: any;\n  let slaveEnd: any;\n  let term: CoreBrowserTerminal;\n  let customHandler: IDisposable | undefined;\n\n  before(() => {\n    if (process.platform === 'win32') {\n      return;\n    }\n    ptyTerm = (pty as any).open({cols: COLS, rows: ROWS});\n    slaveEnd = ptyTerm._slave;\n    term = new CoreBrowserTerminal({cols: COLS, rows: ROWS});\n    ptyTerm._master.on('data', (data: string) => term.write(data));\n  });\n\n  after(() => {\n    if (process.platform === 'win32') {\n      return;\n    }\n    ptyTerm._master.end();\n    ptyTerm._master.destroy();\n  });\n\n  for (const filename of FILES) {\n    (process.platform === 'win32' ? it.skip : it)(path.basename(filename), async () => {\n      // reset terminal and handler\n      if (customHandler) {\n        customHandler.dispose();\n      }\n      slaveEnd.write('\\r\\n');\n      term.reset();\n      slaveEnd.write('\\x1bc\\x1b[H');\n\n      // register handler to trigger viewport scraping, wait for it to finish\n      let content = '';\n      const OSC_CODE = 12345;\n      await new Promise<void>(resolve => {\n        customHandler = term.registerOscHandler(OSC_CODE, () => {\n          // grab terminal viewport content\n          content = terminalToString(term);\n          resolve();\n          return true;\n        });\n        // write file to slave\n        slaveEnd.write(fs.readFileSync(filename, 'utf8'));\n        // trigger custom sequence\n        slaveEnd.write(`\\x1b]${OSC_CODE};\\x07`);\n      });\n\n      // compare with expected output (right trimmed)\n      const expected = fs.readFileSync(filename.replace(/\\.in$/, '.text'), 'utf8');\n      const expectedRightTrimmed = expected.split('\\n').map(l => l.replace(/\\s+$/, '')).join('\\n');\n      if (content !== expectedRightTrimmed) {\n        throw new Error(formatError(fs.readFileSync(filename, 'utf8'), content, expected));\n      }\n    });\n  }\n});\n\n/**\n * Helpers\n */\n\n// generate colorful noisy output to compare xterm and emulator cell states\nfunction formatError(input: string, output: string, expected: string): string {\n  function addLineNumber(start: number, color: string): (s: string) => string {\n    let counter = start || 0;\n    return (s: string): string => {\n      counter++;\n      return '\\x1b[33m' + (' ' + counter).slice(-2) + color + s;\n    };\n  }\n  const line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';\n  let s = '';\n  s += `\\n\\x1b[34m${JSON.stringify(input)}`;\n  s += `\\n\\x1b[33m  ${line80}\\n`;\n  s += output.split('\\n').map(addLineNumber(0, '\\x1b[31m')).join('\\n');\n  s += `\\n\\x1b[33m  ${line80}\\n`;\n  s += expected.split('\\n').map(addLineNumber(0, '\\x1b[32m')).join('\\n');\n  return s;\n}\n\n// simple debug output of terminal cells\nfunction terminalToString(term: CoreBrowserTerminal): string {\n  let result = '';\n  let lineText = '';\n  for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {\n    lineText = term.buffer.lines.get(line)!.translateToString(true);\n    // rtrim empty cells as xterm does\n    lineText = lineText.replace(/\\s+$/, '');\n    result += lineText;\n    result += '\\n';\n  }\n  return result;\n}\n"
  },
  {
    "path": "src/browser/TestUtils.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';\nimport { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';\nimport { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';\nimport { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from 'browser/Types';\nimport { IBuffer, IBufferSet } from 'common/buffer/Types';\nimport { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from 'common/Types';\nimport { Buffer } from 'common/buffer/Buffer';\nimport * as Browser from 'common/Platform';\nimport { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';\nimport { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from 'common/services/Services';\nimport { IFunctionIdentifier, IParams } from 'common/parser/Types';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';\nimport { css } from 'common/Color';\nimport { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';\nimport { Emitter, type IEvent } from 'common/Event';\n\nexport class TestTerminal extends CoreBrowserTerminal {\n  public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }\n  public keyDown(ev: any): boolean | undefined { return this._keyDown(ev); }\n  public keyPress(ev: any): boolean { return this._keyPress(ev); }\n  public writeP(data: string | Uint8Array): Promise<void> {\n    return new Promise(r => this.write(data, r));\n  }\n}\n\nexport class MockTerminal implements ITerminal {\n  public onBlur!: IEvent<void>;\n  public onFocus!: IEvent<void>;\n  public onA11yChar!: IEvent<string>;\n  public onWriteParsed!: IEvent<void>;\n  public onA11yTab!: IEvent<number>;\n  public onCursorMove!: IEvent<void>;\n  public onLineFeed!: IEvent<void>;\n  public onSelectionChange!: IEvent<void>;\n  public onData!: IEvent<string>;\n  public onBinary!: IEvent<string>;\n  public onTitleChange!: IEvent<string>;\n  public onBell!: IEvent<void>;\n  public onScroll!: IEvent<number>;\n  public onWillOpen!: IEvent<HTMLElement>;\n  public onKey!: IEvent<{ key: string, domEvent: KeyboardEvent }>;\n  public onRender!: IEvent<{ start: number, end: number }>;\n  public onResize!: IEvent<{ cols: number, rows: number }>;\n  public onDimensionsChange!: IEvent<IRenderDimensionsApi>;\n  public dimensions: IRenderDimensionsApi | undefined;\n  public markers!: IMarker[];\n  public linkifier: ILinkifier2 | undefined;\n  public mouseStateService!: IMouseStateService;\n  public coreService!: ICoreService;\n  public optionsService!: IOptionsService;\n  public unicodeService!: IUnicodeService;\n  public registerMarker(cursorYOffset: number): IMarker {\n    throw new Error('Method not implemented.');\n  }\n  public selectLines(start: number, end: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToLine(line: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public static string: any;\n  public setOption(key: any, value: any): void {\n    throw new Error('Method not implemented.');\n  }\n  public blur(): void {\n    throw new Error('Method not implemented.');\n  }\n  public focus(): void {\n    throw new Error('Method not implemented.');\n  }\n  public input(data: string, wasUserInput: boolean = true): void {\n    throw new Error('Method not implemented.');\n  }\n  public resize(columns: number, rows: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public writeln(data: string): void {\n    throw new Error('Method not implemented.');\n  }\n  public paste(data: string): void {\n    throw new Error('Method not implemented.');\n  }\n  public open(parent: HTMLElement): void {\n    throw new Error('Method not implemented.');\n  }\n  public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {\n    throw new Error('Method not implemented.');\n  }\n  public hasSelection(): boolean {\n    throw new Error('Method not implemented.');\n  }\n  public getSelection(): string {\n    throw new Error('Method not implemented.');\n  }\n  public getSelectionPosition(): IBufferRange | undefined {\n    throw new Error('Method not implemented.');\n  }\n  public clearSelection(): void {\n    throw new Error('Method not implemented.');\n  }\n  public select(column: number, row: number, length: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public selectAll(): void {\n    throw new Error('Method not implemented.');\n  }\n  public dispose(): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollPages(pageCount: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToTop(): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToBottom(): void {\n    throw new Error('Method not implemented.');\n  }\n  public clear(): void {\n    throw new Error('Method not implemented.');\n  }\n  public write(data: string): void {\n    throw new Error('Method not implemented.');\n  }\n  public getBufferElements(startLine: number, endLine?: number | undefined): { bufferElements: HTMLElement[], cursorElement?: HTMLElement | undefined } {\n    throw new Error('Method not implemented.');\n  }\n  public registerBufferElementProvider(bufferProvider: IBufferElementProvider): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public bracketedPasteMode!: boolean;\n  public renderer!: IRenderer;\n  public isFocused!: boolean;\n  public options!: Required<ITerminalOptions>;\n  public element!: HTMLElement;\n  public screenElement!: HTMLElement;\n  public rowContainer!: HTMLElement;\n  public selectionContainer!: HTMLElement;\n  public selectionService!: ISelectionService;\n  public textarea!: HTMLTextAreaElement;\n  public rows!: number;\n  public cols!: number;\n  public browser: IBrowser = Browser as any;\n  public writeBuffer!: string[];\n  public children!: HTMLElement[];\n  public cursorHidden!: boolean;\n  public cursorState!: number;\n  public scrollback!: number;\n  public buffers!: IBufferSet;\n  public buffer!: IBuffer;\n  public viewport!: IViewport;\n  public applicationCursor!: boolean;\n  public handler(data: string): void {\n    throw new Error('Method not implemented.');\n  }\n  public on(event: string, callback: (...args: any[]) => void): void {\n    throw new Error('Method not implemented.');\n  }\n  public off(type: string, listener: XtermListener): void {\n    throw new Error('Method not implemented.');\n  }\n  public addDisposableListener(type: string, handler: XtermListener): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public scrollLines(disp: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToRow(absoluteRow: number): number {\n    throw new Error('Method not implemented.');\n  }\n  public log(text: string): void {\n    throw new Error('Method not implemented.');\n  }\n  public emit(event: string, data: any): void {\n    throw new Error('Method not implemented.');\n  }\n  public reset(): void {\n    throw new Error('Method not implemented.');\n  }\n  public clearTextureAtlas(): void {\n    throw new Error('Method not implemented.');\n  }\n  public refresh(start: number, end: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }\n  public deregisterCharacterJoiner(joinerId: number): void { }\n}\n\nexport class MockBuffer implements IBuffer {\n  public markers!: IMarker[];\n  public addMarker(y: number): IMarker {\n    throw new Error('Method not implemented.');\n  }\n  public isCursorInViewport!: boolean;\n  public lines!: ICircularList<IBufferLine>;\n  public ydisp!: number;\n  public ybase!: number;\n  public hasScrollback!: boolean;\n  public y!: number;\n  public x!: number;\n  public tabs: any;\n  public scrollBottom!: number;\n  public scrollTop!: number;\n  public savedY!: number;\n  public savedX!: number;\n  public savedCharset: ICharset | undefined;\n  public savedCharsets: (ICharset | undefined)[] = [];\n  public savedGlevel: number = 0;\n  public savedOriginMode: boolean = false;\n  public savedWraparoundMode: boolean = true;\n  public savedCurAttrData = new AttributeData();\n  public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {\n    return Buffer.prototype.translateBufferLineToString.apply(this, arguments as any);\n  }\n  public getWrappedRangeForLine(y: number): { first: number, last: number } {\n    return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments as any);\n  }\n  public nextStop(x?: number): number {\n    throw new Error('Method not implemented.');\n  }\n  public prevStop(x?: number): number {\n    throw new Error('Method not implemented.');\n  }\n  public setLines(lines: ICircularList<IBufferLine>): void {\n    this.lines = lines;\n  }\n  public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {\n    return Buffer.prototype.getBlankLine.apply(this, arguments as any);\n  }\n  public getNullCell(attr?: IAttributeData): ICellData {\n    throw new Error('Method not implemented.');\n  }\n  public getWhitespaceCell(attr?: IAttributeData): ICellData {\n    throw new Error('Method not implemented.');\n  }\n  public clearMarkers(y: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public clearAllMarkers(): void {\n    throw new Error('Method not implemented.');\n  }\n}\n\nexport class MockRenderer implements IRenderer {\n  public onRequestRedraw!: IEvent<IRequestRedrawEvent>;\n  public onCanvasResize!: IEvent<{ width: number, height: number }>;\n  public onRender!: IEvent<{ start: number, end: number }>;\n  public dispose(): void {\n    throw new Error('Method not implemented.');\n  }\n  public on(type: string, listener: XtermListener): void {\n    throw new Error('Method not implemented.');\n  }\n  public off(type: string, listener: XtermListener): void {\n    throw new Error('Method not implemented.');\n  }\n  public emit(type: string, data?: any): void {\n    throw new Error('Method not implemented.');\n  }\n  public addDisposableListener(type: string, handler: XtermListener): IDisposable {\n    throw new Error('Method not implemented.');\n  }\n  public dimensions!: IRenderDimensions;\n  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration {\n    throw new Error('Method not implemented.');\n  }\n  public handleResize(cols: number, rows: number): void { }\n  public handleCharSizeChanged(): void { }\n  public handleBlur(): void { }\n  public handleFocus(): void { }\n  public handleSelectionChanged(start: [number, number], end: [number, number]): void { }\n  public handleCursorMove(): void { }\n  public handleOptionsChanged(): void { }\n  public handleDevicePixelRatioChange(): void { }\n  public clear(): void { }\n  public renderRows(start: number, end: number): void { }\n}\n\nexport class MockViewport implements IViewport {\n  private readonly _onRequestScrollLines = new Emitter<{ amount: number, suppressScrollEvent: boolean }>();\n  public readonly onRequestScrollLines = this._onRequestScrollLines.event;\n  public dispose(): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollBarWidth: number = 0;\n  public handleThemeChange(colors: IColorSet): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleWheel(ev: WheelEvent): boolean {\n    throw new Error('Method not implemented.');\n  }\n  public handleTouchStart(ev: TouchEvent): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleTouchMove(ev: TouchEvent): boolean {\n    throw new Error('Method not implemented.');\n  }\n  public syncScrollArea(): void { }\n  public getLinesScrolled(ev: WheelEvent): number {\n    throw new Error('Method not implemented.');\n  }\n  public getBufferElements(startLine: number, endLine?: number | undefined): { bufferElements: HTMLElement[], cursorElement?: HTMLElement | undefined } {\n    throw new Error('Method not implemented.');\n  }\n  public scrollLines(disp: number): void {\n    this._onRequestScrollLines.fire({ amount: disp, suppressScrollEvent: false });\n  }\n  public reset(): void {\n  }\n}\n\nexport class MockCompositionHelper implements ICompositionHelper {\n  public get isComposing(): boolean {\n    return false;\n  }\n  public compositionstart(): void {\n    throw new Error('Method not implemented.');\n  }\n  public compositionupdate(ev: CompositionEvent): void {\n    throw new Error('Method not implemented.');\n  }\n  public compositionend(): void {\n    throw new Error('Method not implemented.');\n  }\n  public updateCompositionElements(dontRecurse?: boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public keydown(ev: KeyboardEvent): boolean {\n    return true;\n  }\n}\n\nexport class MockCoreBrowserService implements ICoreBrowserService {\n  public onDprChange = new Emitter<number>().event;\n  public onWindowChange = new Emitter<Window & typeof globalThis>().event;\n  public serviceBrand: undefined;\n  public isFocused: boolean = true;\n  public get window(): Window & typeof globalThis {\n    throw Error('Window object not available in tests');\n  }\n  public get mainDocument(): Document {\n    throw Error('Document object not available in tests');\n  }\n  public dpr: number = 1;\n}\n\nexport class MockCharSizeService implements ICharSizeService {\n  public serviceBrand: undefined;\n  public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }\n  public onCharSizeChange: IEvent<void> = new Emitter<void>().event;\n  constructor(public width: number, public height: number) {}\n  public measure(): void {}\n}\n\nexport class MockMouseService implements IMouseService {\n  public serviceBrand: undefined;\n  public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {\n    throw new Error('Not implemented');\n  }\n\n  public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {\n    throw new Error('Not implemented');\n  }\n\n  public bindMouse(): void { }\n  public reset(): void { }\n}\n\nexport class MockRenderService implements IRenderService {\n  public serviceBrand: undefined;\n  public onDimensionsChange: IEvent<IRenderDimensions> = new Emitter<IRenderDimensions>().event;\n  public onRenderedViewportChange: IEvent<{ start: number, end: number }> = new Emitter<{ start: number, end: number }>().event;\n  public onRender: IEvent<{ start: number, end: number }> = new Emitter<{ start: number, end: number }>().event;\n  public onRefreshRequest: IEvent<{ start: number, end: number}> = new Emitter<{ start: number, end: number }>().event;\n  public dimensions: IRenderDimensions = createRenderDimensions();\n  public refreshRows(start: number, end: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public addRefreshCallback(callback: FrameRequestCallback): number {\n    throw new Error('Method not implemented.');\n  }\n  public clearTextureAtlas(): void {\n    throw new Error('Method not implemented.');\n  }\n  public resize(cols: number, rows: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public hasRenderer(): boolean {\n    throw new Error('Method not implemented.');\n  }\n  public setRenderer(renderer: IRenderer): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleDevicePixelRatioChange(): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleResize(cols: number, rows: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleCharSizeChanged(): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleBlur(): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleFocus(): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleCursorMove(): void {\n    throw new Error('Method not implemented.');\n  }\n  public clear(): void {\n    throw new Error('Method not implemented.');\n  }\n  public dispose(): void {\n    throw new Error('Method not implemented.');\n  }\n  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration {\n    throw new Error('Method not implemented.');\n  }\n}\n\nexport class MockCharacterJoinerService implements ICharacterJoinerService {\n  public serviceBrand: undefined;\n  public register(handler: (text: string) => [number, number][]): number {\n    return 0;\n  }\n  public deregister(joinerId: number): boolean {\n    return true;\n  }\n  public getJoinedCharacters(row: number): [number, number][] {\n    return [];\n  }\n}\n\nexport class MockSelectionService implements ISelectionService {\n  public serviceBrand: undefined;\n  public selectionText: string = '';\n  public hasSelection: boolean = false;\n  public selectionStart: [number, number] | undefined;\n  public selectionEnd: [number, number] | undefined;\n  public onLinuxMouseSelection = new Emitter<string>().event;\n  public onRequestRedraw = new Emitter<ISelectionRedrawRequestEvent>().event;\n  public onRequestScrollLines = new Emitter<ISelectionRequestScrollLinesEvent>().event;\n  public onSelectionChange = new Emitter<void>().event;\n  public disable(): void {\n    throw new Error('Method not implemented.');\n  }\n  public enable(): void {\n    throw new Error('Method not implemented.');\n  }\n  public reset(): void {\n    throw new Error('Method not implemented.');\n  }\n  public setSelection(row: number, col: number, length: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public selectAll(): void {\n    throw new Error('Method not implemented.');\n  }\n  public selectLines(start: number, end: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public clearSelection(): void {\n    throw new Error('Method not implemented.');\n  }\n  public rightClickSelect(event: MouseEvent): void {\n    throw new Error('Method not implemented.');\n  }\n  public shouldColumnSelect(event: MouseEvent | KeyboardEvent): boolean {\n    throw new Error('Method not implemented.');\n  }\n  public shouldForceSelection(event: MouseEvent): boolean {\n    throw new Error('Method not implemented.');\n  }\n  public refresh(isLinuxMouseSelection?: boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public handleMouseDown(event: MouseEvent): void {\n    throw new Error('Method not implemented.');\n  }\n  public isCellInSelection(x: number, y: number): boolean {\n    return false;\n  }\n}\n\nexport class MockThemeService implements IThemeService{\n  public serviceBrand: undefined;\n  public onChangeColors = new Emitter<ReadonlyColorSet>().event;\n  public restoreColor(slot?: ColorIndex | undefined): void {\n    throw new Error('Method not implemented.');\n  }\n  public modifyColors(callback: (colors: IColorSet) => void): void {\n    throw new Error('Method not implemented.');\n  }\n  public colors: ReadonlyColorSet = {\n    background: css.toColor('#010101'),\n    foreground: css.toColor('#020202'),\n    ansi: [\n      // dark:\n      css.toColor('#2e3436'),\n      css.toColor('#cc0000'),\n      css.toColor('#4e9a06'),\n      css.toColor('#c4a000'),\n      css.toColor('#3465a4'),\n      css.toColor('#75507b'),\n      css.toColor('#06989a'),\n      css.toColor('#d3d7cf'),\n      // bright:\n      css.toColor('#555753'),\n      css.toColor('#ef2929'),\n      css.toColor('#8ae234'),\n      css.toColor('#fce94f'),\n      css.toColor('#729fcf'),\n      css.toColor('#ad7fa8'),\n      css.toColor('#34e2e2'),\n      css.toColor('#eeeeec')\n    ],\n    selectionBackgroundOpaque: css.toColor('#ff0000'),\n    selectionInactiveBackgroundOpaque: css.toColor('#00ff00')\n  } as any;\n}\n"
  },
  {
    "path": "src/browser/TimeBasedDebouncer.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second\n\nimport { IRenderDebouncer } from 'browser/Types';\n\n/**\n * Debounces calls to update screen readers to update at most once configurable interval of time.\n */\nexport class TimeBasedDebouncer implements IRenderDebouncer {\n  private _rowStart: number | undefined;\n  private _rowEnd: number | undefined;\n  private _rowCount: number | undefined;\n\n  // The last moment that the Terminal was refreshed at\n  private _lastRefreshMs = 0;\n  // Whether a trailing refresh should be triggered due to a refresh request that was throttled\n  private _additionalRefreshRequested = false;\n\n  private _refreshTimeoutID: number | undefined;\n\n  constructor(\n    private _renderCallback: (start: number, end: number) => void,\n    private readonly _debounceThresholdMS = RENDER_DEBOUNCE_THRESHOLD_MS\n  ) {\n  }\n\n  public dispose(): void {\n    if (this._refreshTimeoutID) {\n      clearTimeout(this._refreshTimeoutID);\n    }\n  }\n\n  public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {\n    this._rowCount = rowCount;\n    // Get the min/max row start/end for the arg values\n    rowStart = rowStart ?? 0;\n    rowEnd = rowEnd ?? this._rowCount - 1;\n    // Set the properties to the updated values\n    this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;\n    this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;\n\n    // Only refresh if the time since last refresh is above a threshold, otherwise wait for\n    // enough time to pass before refreshing again.\n    const refreshRequestTime: number = performance.now();\n    if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {\n      // Enough time has lapsed since the last refresh; refresh immediately\n      this._lastRefreshMs = refreshRequestTime;\n      this._innerRefresh();\n    } else if (!this._additionalRefreshRequested) {\n      // This is the first additional request throttled; set up trailing refresh\n      const elapsed = refreshRequestTime - this._lastRefreshMs;\n      const waitPeriodBeforeTrailingRefresh = this._debounceThresholdMS - elapsed;\n      this._additionalRefreshRequested = true;\n\n      this._refreshTimeoutID = window.setTimeout(() => {\n        this._lastRefreshMs = performance.now();\n        this._innerRefresh();\n        this._additionalRefreshRequested = false;\n        this._refreshTimeoutID = undefined; // No longer need to clear the timeout\n      }, waitPeriodBeforeTrailingRefresh);\n    }\n  }\n\n  private _innerRefresh(): void {\n    // Make sure values are set\n    if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {\n      return;\n    }\n\n    // Clamp values\n    const start = Math.max(this._rowStart, 0);\n    const end = Math.min(this._rowEnd, this._rowCount - 1);\n\n    // Reset debouncer (this happens before render callback as the render could trigger it again)\n    this._rowStart = undefined;\n    this._rowEnd = undefined;\n\n    // Run render callback\n    this._renderCallback(start, end);\n  }\n}\n\n"
  },
  {
    "path": "src/browser/Types.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CharData, IColor, ICoreTerminal, ITerminalOptions } from 'common/Types';\nimport { IBuffer } from 'common/buffer/Types';\nimport { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm';\nimport { channels, css } from 'common/Color';\nimport type { IEvent } from 'common/Event';\n\n/**\n * A portion of the public API that are implemented identially internally and simply passed through.\n */\ntype InternalPassthroughApis = Omit<ITerminalApi, 'buffer' | 'parser' | 'unicode' | 'modes' | 'writeln' | 'loadAddon'>;\n\nexport interface ITerminal extends InternalPassthroughApis, ICoreTerminal {\n  screenElement: HTMLElement | undefined;\n  browser: IBrowser;\n  buffer: IBuffer;\n  linkifier: ILinkifier2 | undefined;\n  options: Required<ITerminalOptions>;\n\n  readonly dimensions: IRenderDimensionsApi | undefined;\n\n  onBlur: IEvent<void>;\n  onFocus: IEvent<void>;\n  onDimensionsChange: IEvent<IRenderDimensionsApi>;\n  onA11yChar: IEvent<string>;\n  onA11yTab: IEvent<number>;\n  onWillOpen: IEvent<HTMLElement>;\n}\n\nexport type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;\nexport type CustomWheelEventHandler = (event: WheelEvent) => boolean;\n\nexport type LineData = CharData[];\n\nexport interface ICompositionHelper {\n  readonly isComposing: boolean;\n  compositionstart(): void;\n  compositionupdate(ev: CompositionEvent): void;\n  compositionend(): void;\n  updateCompositionElements(dontRecurse?: boolean): void;\n  keydown(ev: KeyboardEvent): boolean;\n}\n\nexport interface IBrowser {\n  isNode: boolean;\n  userAgent: string;\n  platform: string;\n  isFirefox: boolean;\n  isMac: boolean;\n  isIpad: boolean;\n  isIphone: boolean;\n  isWindows: boolean;\n}\n\nexport interface IColorSet {\n  foreground: IColor;\n  background: IColor;\n  cursor: IColor;\n  cursorAccent: IColor;\n  selectionForeground: IColor | undefined;\n  selectionBackgroundTransparent: IColor;\n  /** The selection blended on top of background. */\n  selectionBackgroundOpaque: IColor;\n  selectionInactiveBackgroundTransparent: IColor;\n  selectionInactiveBackgroundOpaque: IColor;\n  scrollbarSliderBackground: IColor;\n  scrollbarSliderHoverBackground: IColor;\n  scrollbarSliderActiveBackground: IColor;\n  overviewRulerBorder: IColor;\n  ansi: IColor[];\n  /** Maps original colors to colors that respect minimum contrast ratio. */\n  contrastCache: IColorContrastCache;\n  /** Maps original colors to colors that respect _half_ of the minimum contrast ratio. */\n  halfContrastCache: IColorContrastCache;\n}\n\nexport type ReadonlyColorSet = Readonly<Omit<IColorSet, 'ansi'>> & { ansi: Readonly<Pick<IColorSet, 'ansi'>['ansi']> };\n\nexport interface IColorContrastCache {\n  clear(): void;\n  setCss(bg: number, fg: number, value: string | null): void;\n  getCss(bg: number, fg: number): string | null | undefined;\n  setColor(bg: number, fg: number, value: IColor | null): void;\n  getColor(bg: number, fg: number): IColor | null | undefined;\n}\n\nexport interface IPartialColorSet {\n  foreground: IColor;\n  background: IColor;\n  cursor?: IColor;\n  cursorAccent?: IColor;\n  selectionBackground?: IColor;\n  ansi: IColor[];\n}\n\nexport interface IViewport extends IDisposable {\n  scrollBarWidth: number;\n  readonly onRequestScrollLines: IEvent<{ amount: number, suppressScrollEvent: boolean }>;\n  syncScrollArea(immediate?: boolean, force?: boolean): void;\n  getLinesScrolled(ev: WheelEvent): number;\n  getBufferElements(startLine: number, endLine?: number): { bufferElements: HTMLElement[], cursorElement?: HTMLElement };\n  handleWheel(ev: WheelEvent): boolean;\n  handleTouchStart(ev: TouchEvent): void;\n  handleTouchMove(ev: TouchEvent): boolean;\n  scrollLines(disp: number): void;  // todo api name?\n  reset(): void;\n}\n\nexport interface ILinkifierEvent {\n  x1: number;\n  y1: number;\n  x2: number;\n  y2: number;\n  cols: number;\n  fg: number | undefined;\n}\n\ninterface ILinkState {\n  decorations: ILinkDecorations;\n  isHovered: boolean;\n}\nexport interface ILinkWithState {\n  link: ILink;\n  state?: ILinkState;\n}\n\nexport interface ILinkifier2 extends IDisposable {\n  onShowLinkUnderline: IEvent<ILinkifierEvent>;\n  onHideLinkUnderline: IEvent<ILinkifierEvent>;\n  readonly currentLink: ILinkWithState | undefined;\n}\n\nexport interface ILink {\n  range: IBufferRange;\n  text: string;\n  decorations?: ILinkDecorations;\n  activate(event: MouseEvent, text: string): void;\n  hover?(event: MouseEvent, text: string): void;\n  leave?(event: MouseEvent, text: string): void;\n  dispose?(): void;\n}\n\nexport interface ILinkDecorations {\n  pointerCursor: boolean;\n  underline: boolean;\n}\n\nexport interface IBufferRange {\n  start: IBufferCellPosition;\n  end: IBufferCellPosition;\n}\n\nexport interface IBufferCellPosition {\n  x: number;\n  y: number;\n}\n\nexport type CharacterJoinerHandler = (text: string) => [number, number][];\n\nexport interface ICharacterJoiner {\n  id: number;\n  handler: CharacterJoinerHandler;\n}\n\nexport interface IRenderDebouncer extends IDisposable {\n  refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void;\n}\n\nexport interface IRenderDebouncerWithCallback extends IRenderDebouncer {\n  addRefreshCallback(callback: FrameRequestCallback): number;\n}\n\nexport interface IBufferElementProvider {\n  provideBufferElements(): DocumentFragment | HTMLElement;\n}\n\n// An IIFE to generate DEFAULT_ANSI_COLORS.\nexport const DEFAULT_ANSI_COLORS = Object.freeze((() => {\n  const colors = [\n    // dark:\n    css.toColor('#2e3436'),\n    css.toColor('#cc0000'),\n    css.toColor('#4e9a06'),\n    css.toColor('#c4a000'),\n    css.toColor('#3465a4'),\n    css.toColor('#75507b'),\n    css.toColor('#06989a'),\n    css.toColor('#d3d7cf'),\n    // bright:\n    css.toColor('#555753'),\n    css.toColor('#ef2929'),\n    css.toColor('#8ae234'),\n    css.toColor('#fce94f'),\n    css.toColor('#729fcf'),\n    css.toColor('#ad7fa8'),\n    css.toColor('#34e2e2'),\n    css.toColor('#eeeeec')\n  ];\n\n  // Fill in the remaining 240 ANSI colors.\n  // Generate colors (16-231)\n  const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];\n  for (let i = 0; i < 216; i++) {\n    const r = v[(i / 36) % 6 | 0];\n    const g = v[(i / 6) % 6 | 0];\n    const b = v[i % 6];\n    colors.push({\n      css: channels.toCss(r, g, b),\n      rgba: channels.toRgba(r, g, b)\n    });\n  }\n\n  // Generate greys (232-255)\n  for (let i = 0; i < 24; i++) {\n    const c = 8 + i * 10;\n    colors.push({\n      css: channels.toCss(c, c, c),\n      rgba: channels.toRgba(c, c, c)\n    });\n  }\n\n  return colors;\n})());\n"
  },
  {
    "path": "src/browser/Viewport.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';\nimport { ViewportConstants } from 'browser/shared/Constants';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IBufferService, ICoreService, IMouseStateService, IOptionsService } from 'common/services/Services';\nimport { CoreMouseEventType } from 'common/Types';\nimport { scheduleAtNextAnimationFrame } from 'browser/Dom';\nimport { SmoothScrollableElement } from 'browser/scrollable/scrollableElement';\nimport type { IScrollableElementChangeOptions } from 'browser/scrollable/scrollableElementOptions';\nimport { Emitter, EventUtils } from 'common/Event';\nimport { Scrollable, ScrollbarVisibility, type IScrollEvent } from 'browser/scrollable/scrollable';\n\nexport class Viewport extends Disposable {\n\n  protected _onRequestScrollLines = this._register(new Emitter<number>());\n  public readonly onRequestScrollLines = this._onRequestScrollLines.event;\n\n  private _scrollableElement: SmoothScrollableElement;\n  private _styleElement: HTMLStyleElement;\n\n  private _queuedAnimationFrame?: number;\n  private _latestYDisp?: number;\n  private _isSyncing: boolean = false;\n  private _isHandlingScroll: boolean = false;\n  private _suppressOnScrollHandler: boolean = false;\n  private _needsSyncOnRender: boolean = false;\n\n  constructor(\n    element: HTMLElement,\n    screenElement: HTMLElement,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @ICoreBrowserService coreBrowserService: ICoreBrowserService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @IMouseStateService mouseStateService: IMouseStateService,\n    @IThemeService themeService: IThemeService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @IRenderService private readonly _renderService: IRenderService\n  ) {\n    super();\n\n    const scrollable = this._register(new Scrollable({\n      forceIntegerValues: false,\n      smoothScrollDuration: this._optionsService.rawOptions.smoothScrollDuration,\n      // This is used over `IRenderService.addRefreshCallback` since it can be canceled\n      scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(coreBrowserService.window, cb)\n    }));\n    this._register(this._optionsService.onSpecificOptionChange('smoothScrollDuration', () => {\n      scrollable.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration);\n    }));\n\n    this._scrollableElement = this._register(new SmoothScrollableElement(screenElement, {\n      vertical: ScrollbarVisibility.AUTO,\n      horizontal: ScrollbarVisibility.HIDDEN,\n      useShadows: false,\n      mouseWheelSmoothScroll: true,\n      verticalHasArrows: this._optionsService.rawOptions.scrollbar?.showArrows ?? false,\n      ...this._getChangeOptions()\n    }, scrollable));\n    this._register(this._optionsService.onMultipleOptionChange([\n      'scrollSensitivity',\n      'fastScrollSensitivity',\n      'scrollbar'\n    ], () => this._scrollableElement.updateOptions(this._getChangeOptions())));\n    // Don't handle mouse wheel if wheel events are supported by the current mouse prototcol\n    this._register(mouseStateService.onProtocolChange(type => {\n      this._scrollableElement.updateOptions({\n        handleMouseWheel: !(type & CoreMouseEventType.WHEEL)\n      });\n    }));\n\n    this._scrollableElement.setScrollDimensions({ height: 0, scrollHeight: 0 });\n    this._register(EventUtils.runAndSubscribe(themeService.onChangeColors, () => {\n      element.style.backgroundColor = themeService.colors.background.css;\n      this._scrollableElement.getDomNode().style.backgroundColor = themeService.colors.background.css;\n    }));\n    element.appendChild(this._scrollableElement.getDomNode());\n    this._register(toDisposable(() => this._scrollableElement.getDomNode().remove()));\n\n    this._styleElement = coreBrowserService.mainDocument.createElement('style');\n    screenElement.appendChild(this._styleElement);\n    this._register(toDisposable(() => this._styleElement.remove()));\n    this._register(EventUtils.runAndSubscribe(themeService.onChangeColors, () => {\n      this._styleElement.textContent = [\n        `.xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider {`,\n        `  background: ${themeService.colors.scrollbarSliderBackground.css};`,\n        `}`,\n        `.xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider:hover {`,\n        `  background: ${themeService.colors.scrollbarSliderHoverBackground.css};`,\n        `}`,\n        `.xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider.xterm-active {`,\n        `  background: ${themeService.colors.scrollbarSliderActiveBackground.css};`,\n        `}`\n      ].join('\\n');\n    }));\n\n    this._register(this._bufferService.onResize(() => this.queueSync()));\n    this._register(this._bufferService.buffers.onBufferActivate(() => {\n      // Reset _latestYDisp when switching buffers to prevent stale scroll position\n      // from alt buffer contaminating normal buffer scroll position\n      this._latestYDisp = undefined;\n      this.queueSync();\n    }));\n    this._register(this._bufferService.onScroll(() => this._sync()));\n\n    // Flush deferred viewport sync after a render completes (e.g. after ESU ends\n    // synchronized output mode). This ensures DOM scroll position updates atomically\n    // with the canvas render.\n    this._register(this._renderService.onRender(() => {\n      if (this._needsSyncOnRender) {\n        this._needsSyncOnRender = false;\n        this._sync();\n      }\n    }));\n\n    this._register(this._scrollableElement.onScroll(e => this._handleScroll(e)));\n\n  }\n\n  public scrollLines(disp: number): void {\n    const pos = this._scrollableElement.getScrollPosition();\n    this._scrollableElement.setScrollPosition({\n      reuseAnimation: true,\n      scrollTop: pos.scrollTop + disp * this._renderService.dimensions.css.cell.height\n    });\n  }\n\n  public scrollToLine(line: number, disableSmoothScroll?: boolean): void {\n    if (disableSmoothScroll) {\n      this._latestYDisp = line;\n    }\n    this._scrollableElement.setScrollPosition({\n      reuseAnimation: !disableSmoothScroll,\n      scrollTop: line * this._renderService.dimensions.css.cell.height\n    });\n  }\n\n  private _getChangeOptions(): IScrollableElementChangeOptions {\n    const showScrollbar = this._optionsService.rawOptions.scrollbar?.showScrollbar ?? true;\n    const showArrows = this._optionsService.rawOptions.scrollbar?.showArrows ?? false;\n    const verticalScrollbarSize = showScrollbar\n      ? (this._optionsService.rawOptions.scrollbar?.width ?? ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH)\n      : 0;\n    return {\n      mouseWheelScrollSensitivity: this._optionsService.rawOptions.scrollSensitivity,\n      fastScrollSensitivity: this._optionsService.rawOptions.fastScrollSensitivity,\n      vertical: showScrollbar ? ScrollbarVisibility.AUTO : ScrollbarVisibility.HIDDEN,\n      verticalScrollbarSize,\n      verticalHasArrows: showArrows\n    };\n  }\n\n  public queueSync(ydisp?: number): void {\n    // Update state\n    if (ydisp !== undefined) {\n      this._latestYDisp = ydisp;\n    }\n\n    // Don't queue more than one callback\n    if (this._queuedAnimationFrame !== undefined) {\n      return;\n    }\n    this._queuedAnimationFrame = this._renderService.addRefreshCallback(() => {\n      this._queuedAnimationFrame = undefined;\n      this._sync(this._latestYDisp);\n    });\n  }\n\n  private _sync(ydisp: number = this._bufferService.buffer.ydisp): void {\n    if (!this._renderService || this._isSyncing) {\n      return;\n    }\n    // Defer DOM scroll updates during synchronized output to prevent visible\n    // scroll position flickering while the canvas content is frozen.\n    if (this._coreService.decPrivateModes.synchronizedOutput) {\n      this._needsSyncOnRender = true;\n      return;\n    }\n    this._isSyncing = true;\n\n    // Ignore any onScroll event that happens as a result of dimensions changing as this should\n    // never cause a scrollLines call, only setScrollPosition can do that.\n    this._suppressOnScrollHandler = true;\n    this._scrollableElement.setScrollDimensions({\n      height: this._renderService.dimensions.css.canvas.height,\n      scrollHeight: this._renderService.dimensions.css.cell.height * this._bufferService.buffer.lines.length\n    });\n    this._suppressOnScrollHandler = false;\n\n    // If ydisp has been changed by some other component (input/buffer), then stop animating smooth\n    // scroll and scroll there immediately.\n    if (ydisp !== this._latestYDisp) {\n      this._scrollableElement.setScrollPosition({\n        scrollTop: ydisp * this._renderService.dimensions.css.cell.height\n      });\n    }\n\n    this._isSyncing = false;\n  }\n\n  private _handleScroll(e: IScrollEvent): void {\n    if (!this._renderService) {\n      return;\n    }\n    if (this._isHandlingScroll || this._suppressOnScrollHandler) {\n      return;\n    }\n    this._isHandlingScroll = true;\n    const newRow = Math.round(e.scrollTop / this._renderService.dimensions.css.cell.height);\n    const diff = newRow - this._bufferService.buffer.ydisp;\n    if (diff !== 0) {\n      this._latestYDisp = newRow;\n      this._onRequestScrollLines.fire(diff);\n    }\n    this._isHandlingScroll = false;\n  }\n\n  public handleTouchScroll(translationY: number): void {\n    const pos = this._scrollableElement.getScrollPosition();\n    this._scrollableElement.setScrollPosition({\n      scrollTop: pos.scrollTop - translationY\n    });\n  }\n}\n"
  },
  {
    "path": "src/browser/decorations/BufferDecorationRenderer.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { ICoreBrowserService, IRenderService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';\n\nexport class BufferDecorationRenderer extends Disposable {\n  private readonly _container: HTMLElement;\n  private readonly _decorationElements: Map<IInternalDecoration, HTMLElement> = new Map();\n\n  private _animationFrame: number | undefined;\n  private _altBufferIsActive: boolean = false;\n  private _dimensionsChanged: boolean = false;\n\n  constructor(\n    private readonly _screenElement: HTMLElement,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n    @IDecorationService private readonly _decorationService: IDecorationService,\n    @IRenderService private readonly _renderService: IRenderService\n  ) {\n    super();\n\n    this._container = document.createElement('div');\n    this._container.classList.add('xterm-decoration-container');\n    this._screenElement.appendChild(this._container);\n\n    this._register(this._renderService.onRenderedViewportChange(() => this._doRefreshDecorations()));\n    this._register(this._renderService.onDimensionsChange(() => {\n      this._dimensionsChanged = true;\n      this._queueRefresh();\n    }));\n    this._register(this._coreBrowserService.onDprChange(() => this._queueRefresh()));\n    this._register(this._bufferService.buffers.onBufferActivate(() => {\n      this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;\n    }));\n    this._register(this._decorationService.onDecorationRegistered(() => this._queueRefresh()));\n    this._register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));\n    this._register(toDisposable(() => {\n      this._container.remove();\n      this._decorationElements.clear();\n    }));\n  }\n\n  private _queueRefresh(): void {\n    if (this._animationFrame !== undefined) {\n      return;\n    }\n    this._animationFrame = this._renderService.addRefreshCallback(() => {\n      this._doRefreshDecorations();\n      this._animationFrame = undefined;\n    });\n  }\n\n  private _doRefreshDecorations(): void {\n    for (const decoration of this._decorationService.decorations) {\n      this._renderDecoration(decoration);\n    }\n    this._dimensionsChanged = false;\n  }\n\n  private _renderDecoration(decoration: IInternalDecoration): void {\n    this._refreshStyle(decoration);\n    if (this._dimensionsChanged) {\n      this._refreshXPosition(decoration);\n    }\n  }\n\n  private _createElement(decoration: IInternalDecoration): HTMLElement {\n    const element = this._coreBrowserService.mainDocument.createElement('div');\n    element.classList.add('xterm-decoration');\n    element.classList.toggle('xterm-decoration-top-layer', decoration?.options?.layer === 'top');\n    element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`;\n    element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.css.cell.height}px`;\n    element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.css.cell.height}px`;\n    element.style.lineHeight = `${this._renderService.dimensions.css.cell.height}px`;\n\n    const x = decoration.options.x ?? 0;\n    if (x && x > this._bufferService.cols) {\n      // exceeded the container width, so hide\n      element.style.display = 'none';\n    }\n    this._refreshXPosition(decoration, element);\n\n    return element;\n  }\n\n  private _refreshStyle(decoration: IInternalDecoration): void {\n    const line = decoration.marker.line - this._bufferService.buffers.active.ydisp;\n    if (line < 0 || line >= this._bufferService.rows) {\n      // outside of viewport\n      if (decoration.element) {\n        decoration.element.style.display = 'none';\n        decoration.onRenderEmitter.fire(decoration.element);\n      }\n    } else {\n      let element = this._decorationElements.get(decoration);\n      if (!element) {\n        element = this._createElement(decoration);\n        decoration.element = element;\n        this._decorationElements.set(decoration, element);\n        this._container.appendChild(element);\n        decoration.onDispose(() => {\n          this._decorationElements.delete(decoration);\n          element!.remove();\n        });\n      }\n      element.style.display = this._altBufferIsActive ? 'none' : 'block';\n      if (!this._altBufferIsActive) {\n        element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`;\n        element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.css.cell.height}px`;\n        element.style.top = `${line * this._renderService.dimensions.css.cell.height}px`;\n        element.style.lineHeight = `${this._renderService.dimensions.css.cell.height}px`;\n      }\n      decoration.onRenderEmitter.fire(element);\n    }\n  }\n\n  private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement | undefined = decoration.element): void {\n    if (!element) {\n      return;\n    }\n    const x = decoration.options.x ?? 0;\n    if ((decoration.options.anchor || 'left') === 'right') {\n      element.style.right = x ? `${x * this._renderService.dimensions.css.cell.width}px` : '';\n    } else {\n      element.style.left = x ? `${x * this._renderService.dimensions.css.cell.width}px` : '';\n    }\n  }\n\n  private _removeDecoration(decoration: IInternalDecoration): void {\n    this._decorationElements.get(decoration)?.remove();\n    this._decorationElements.delete(decoration);\n    decoration.dispose();\n  }\n}\n"
  },
  {
    "path": "src/browser/decorations/ColorZoneStore.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { ColorZoneStore } from 'browser/decorations/ColorZoneStore';\n\nconst optionsRedFull = {\n  overviewRulerOptions: {\n    color: 'red',\n    position: 'full' as const\n  }\n};\n\ndescribe('ColorZoneStore', () => {\n  let store: ColorZoneStore;\n\n  beforeEach(() => {\n    store = new ColorZoneStore();\n    store.setPadding({\n      full: 1,\n      left: 1,\n      center: 1,\n      right: 1\n    });\n  });\n\n  it('should merge adjacent zones', () => {\n    store.addDecoration({\n      marker: { line: 0 },\n      options: optionsRedFull\n    });\n    store.addDecoration({\n      marker: { line: 1 },\n      options: optionsRedFull\n    });\n    assert.deepStrictEqual(store.zones, [\n      {\n        color: 'red',\n        position: 'full',\n        startBufferLine: 0,\n        endBufferLine: 1\n      }\n    ]);\n  });\n\n  it('should not merge non-adjacent zones', () => {\n    store.addDecoration({\n      marker: { line: 0 },\n      options: optionsRedFull\n    });\n    store.addDecoration({\n      marker: { line: 2 },\n      options: optionsRedFull\n    });\n    assert.deepStrictEqual(store.zones, [\n      {\n        color: 'red',\n        position: 'full',\n        startBufferLine: 0,\n        endBufferLine: 0\n      },\n      {\n        color: 'red',\n        position: 'full',\n        startBufferLine: 2,\n        endBufferLine: 2\n      }\n    ]);\n  });\n\n  it('should reuse zone objects', () => {\n    const obj = {\n      marker: { line: 0 },\n      options: optionsRedFull\n    };\n    store.addDecoration(obj);\n    const zone = store.zones[0];\n    store.clear();\n    store.addDecoration({\n      marker: { line: 1 },\n      options: optionsRedFull\n    });\n    // The object reference should be the same\n    assert.equal(zone, store.zones[0]);\n  });\n});\n"
  },
  {
    "path": "src/browser/decorations/ColorZoneStore.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { IInternalDecoration } from 'common/services/Services';\n\nexport interface IColorZoneStore {\n  readonly zones: IColorZone[];\n  clear(): void;\n  addDecoration(decoration: IInternalDecoration): void;\n  /**\n   * Sets the amount of padding in lines that will be added between zones, if new lines intersect\n   * the padding they will be merged into the same zone.\n   */\n  setPadding(padding: { [position: string]: number }): void;\n}\n\nexport interface IColorZone {\n  /** Color in a format supported by canvas' fillStyle. */\n  color: string;\n  position: 'full' | 'left' | 'center' | 'right' | undefined;\n  startBufferLine: number;\n  endBufferLine: number;\n}\n\ninterface IMinimalDecorationForColorZone {\n  marker: Pick<IInternalDecoration['marker'], 'line'>;\n  options: Pick<IInternalDecoration['options'], 'overviewRulerOptions'>;\n}\n\nexport class ColorZoneStore implements IColorZoneStore {\n  private _zones: IColorZone[] = [];\n\n  // The zone pool is used to keep zone objects from being freed between clearing the color zone\n  // store and fetching the zones. This helps reduce GC pressure since the color zones are\n  // accumulated on potentially every scroll event.\n  private _zonePool: IColorZone[] = [];\n  private _zonePoolIndex = 0;\n\n  private _linePadding: { [position: string]: number } = {\n    full: 0,\n    left: 0,\n    center: 0,\n    right: 0\n  };\n\n  public get zones(): IColorZone[] {\n    // Trim the zone pool to free unused memory\n    this._zonePool.length = Math.min(this._zonePool.length, this._zones.length);\n    return this._zones;\n  }\n\n  public clear(): void {\n    this._zones.length = 0;\n    this._zonePoolIndex = 0;\n  }\n\n  public addDecoration(decoration: IMinimalDecorationForColorZone): void {\n    if (!decoration.options.overviewRulerOptions) {\n      return;\n    }\n    for (const z of this._zones) {\n      if (z.color === decoration.options.overviewRulerOptions.color &&\n          z.position === decoration.options.overviewRulerOptions.position) {\n        if (this._lineIntersectsZone(z, decoration.marker.line)) {\n          return;\n        }\n        if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) {\n          this._addLineToZone(z, decoration.marker.line);\n          return;\n        }\n      }\n    }\n    // Create using zone pool if possible\n    if (this._zonePoolIndex < this._zonePool.length) {\n      this._zonePool[this._zonePoolIndex].color = decoration.options.overviewRulerOptions.color;\n      this._zonePool[this._zonePoolIndex].position = decoration.options.overviewRulerOptions.position;\n      this._zonePool[this._zonePoolIndex].startBufferLine = decoration.marker.line;\n      this._zonePool[this._zonePoolIndex].endBufferLine = decoration.marker.line;\n      this._zones.push(this._zonePool[this._zonePoolIndex++]);\n      return;\n    }\n    // Create\n    this._zones.push({\n      color: decoration.options.overviewRulerOptions.color,\n      position: decoration.options.overviewRulerOptions.position,\n      startBufferLine: decoration.marker.line,\n      endBufferLine: decoration.marker.line\n    });\n    this._zonePool.push(this._zones[this._zones.length - 1]);\n    this._zonePoolIndex++;\n  }\n\n  public setPadding(padding: { [position: string]: number }): void {\n    this._linePadding = padding;\n  }\n\n  private _lineIntersectsZone(zone: IColorZone, line: number): boolean {\n    return (\n      line >= zone.startBufferLine &&\n      line <= zone.endBufferLine\n    );\n  }\n\n  private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean {\n    return (\n      (line >= zone.startBufferLine - this._linePadding[position || 'full']) &&\n      (line <= zone.endBufferLine + this._linePadding[position || 'full'])\n    );\n  }\n\n  private _addLineToZone(zone: IColorZone, line: number): void {\n    zone.startBufferLine = Math.min(zone.startBufferLine, line);\n    zone.endBufferLine = Math.max(zone.endBufferLine, line);\n  }\n}\n"
  },
  {
    "path": "src/browser/decorations/OverviewRulerRenderer.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';\nimport { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';\n\nconst enum Constants {\n  OVERVIEW_RULER_BORDER_WIDTH = 1\n}\n\n// Helper objects to avoid excessive calculation and garbage collection during rendering. These are\n// static values for each render and can be accessed using the decoration position as the key.\nconst drawHeight = {\n  full: 0,\n  left: 0,\n  center: 0,\n  right: 0\n};\nconst drawWidth = {\n  full: 0,\n  left: 0,\n  center: 0,\n  right: 0\n};\nconst drawX = {\n  full: 0,\n  left: 0,\n  center: 0,\n  right: 0\n};\n\nexport class OverviewRulerRenderer extends Disposable {\n  private readonly _canvas: HTMLCanvasElement;\n  private readonly _ctx: CanvasRenderingContext2D;\n  private readonly _colorZoneStore: IColorZoneStore = new ColorZoneStore();\n  private get _width(): number {\n    const scrollbar = this._optionsService.rawOptions.scrollbar;\n    const showScrollbar = scrollbar?.showScrollbar ?? true;\n    if (!showScrollbar) {\n      return 0;\n    }\n    return scrollbar?.width ?? 0;\n  }\n  private _animationFrame: number | undefined;\n\n  private _shouldUpdateDimensions: boolean | undefined = true;\n  private _shouldUpdateAnchor: boolean | undefined = true;\n  private _lastKnownBufferLength: number = 0;\n\n  constructor(\n    private readonly _viewportElement: HTMLElement,\n    private readonly _screenElement: HTMLElement,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @IDecorationService private readonly _decorationService: IDecorationService,\n    @IRenderService private readonly _renderService: IRenderService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @IThemeService private readonly _themeService: IThemeService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService\n  ) {\n    super();\n    this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');\n    this._canvas.classList.add('xterm-decoration-overview-ruler');\n    this._refreshCanvasDimensions();\n    this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement);\n    this._register(toDisposable(() => this._canvas?.remove()));\n\n    const ctx = this._canvas.getContext('2d');\n    if (!ctx) {\n      throw new Error('Ctx cannot be null');\n    } else {\n      this._ctx = ctx;\n    }\n\n    this._register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true)));\n    this._register(this._decorationService.onDecorationRemoved(() => this._queueRefresh(undefined, true)));\n\n    this._register(this._renderService.onRenderedViewportChange(() => this._queueRefresh()));\n    this._register(this._bufferService.buffers.onBufferActivate(() => {\n      this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';\n    }));\n    this._register(this._bufferService.onScroll(() => {\n      if (this._lastKnownBufferLength !== this._bufferService.buffers.normal.lines.length) {\n        this._refreshDrawHeightConstants();\n        this._refreshColorZonePadding();\n      }\n    }));\n\n    this._register(this._renderService.onDimensionsChange(() => this._queueRefresh(true)));\n\n    this._register(this._coreBrowserService.onDprChange(() => this._queueRefresh(true)));\n    this._register(this._optionsService.onSpecificOptionChange('scrollbar', () => this._queueRefresh(true)));\n    this._register(this._themeService.onChangeColors(() => this._queueRefresh()));\n    this._queueRefresh(true);\n  }\n\n  private _refreshDrawConstants(): void {\n    // width\n    const outerWidth = Math.floor((this._canvas.width - Constants.OVERVIEW_RULER_BORDER_WIDTH) / 3);\n    const innerWidth = Math.ceil((this._canvas.width - Constants.OVERVIEW_RULER_BORDER_WIDTH) / 3);\n    drawWidth.full = this._canvas.width;\n    drawWidth.left = outerWidth;\n    drawWidth.center = innerWidth;\n    drawWidth.right = outerWidth;\n    // height\n    this._refreshDrawHeightConstants();\n    // x\n    drawX.full = Constants.OVERVIEW_RULER_BORDER_WIDTH;\n    drawX.left = Constants.OVERVIEW_RULER_BORDER_WIDTH;\n    drawX.center = Constants.OVERVIEW_RULER_BORDER_WIDTH + drawWidth.left;\n    drawX.right = Constants.OVERVIEW_RULER_BORDER_WIDTH + drawWidth.left + drawWidth.center;\n  }\n\n  private _refreshDrawHeightConstants(): void {\n    drawHeight.full = Math.round(2 * this._coreBrowserService.dpr);\n    // Calculate actual pixels per line\n    const pixelsPerLine = this._canvas.height / this._bufferService.buffer.lines.length;\n    // Clamp actual pixels within a range\n    const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowserService.dpr);\n    drawHeight.left = nonFullHeight;\n    drawHeight.center = nonFullHeight;\n    drawHeight.right = nonFullHeight;\n  }\n\n  private _refreshColorZonePadding(): void {\n    this._colorZoneStore.setPadding({\n      full: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.full),\n      left: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.left),\n      center: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.center),\n      right: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.right)\n    });\n    this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length;\n  }\n\n  private _refreshCanvasDimensions(): void {\n    const cssCanvasHeight = this._renderService.dimensions.css.canvas.height;\n    const deviceCanvasHeight = this._renderService.dimensions.device.canvas.height;\n    this._canvas.style.width = `${this._width}px`;\n    this._canvas.width = Math.round(this._width * this._coreBrowserService.dpr);\n    this._canvas.style.height = `${cssCanvasHeight}px`;\n    this._canvas.height = deviceCanvasHeight;\n    this._refreshDrawConstants();\n    this._refreshColorZonePadding();\n  }\n\n  private _refreshDecorations(): void {\n    if (this._shouldUpdateDimensions) {\n      this._refreshCanvasDimensions();\n    }\n    this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);\n    this._colorZoneStore.clear();\n    for (const decoration of this._decorationService.decorations) {\n      this._colorZoneStore.addDecoration(decoration);\n    }\n    this._ctx.lineWidth = 1;\n    this._renderRulerOutline();\n    const zones = this._colorZoneStore.zones;\n    for (const zone of zones) {\n      if (zone.position !== 'full') {\n        this._renderColorZone(zone);\n      }\n    }\n    for (const zone of zones) {\n      if (zone.position === 'full') {\n        this._renderColorZone(zone);\n      }\n    }\n    this._shouldUpdateDimensions = false;\n    this._shouldUpdateAnchor = false;\n  }\n\n  private _renderRulerOutline(): void {\n    this._ctx.fillStyle = this._themeService.colors.overviewRulerBorder.css;\n    this._ctx.fillRect(0, 0, Constants.OVERVIEW_RULER_BORDER_WIDTH, this._canvas.height);\n    if (this._optionsService.rawOptions.scrollbar?.overviewRuler?.showTopBorder) {\n      this._ctx.fillRect(Constants.OVERVIEW_RULER_BORDER_WIDTH, 0, this._canvas.width - Constants.OVERVIEW_RULER_BORDER_WIDTH, Constants.OVERVIEW_RULER_BORDER_WIDTH);\n    }\n    if (this._optionsService.rawOptions.scrollbar?.overviewRuler?.showBottomBorder) {\n      this._ctx.fillRect(Constants.OVERVIEW_RULER_BORDER_WIDTH, this._canvas.height - Constants.OVERVIEW_RULER_BORDER_WIDTH, this._canvas.width - Constants.OVERVIEW_RULER_BORDER_WIDTH, this._canvas.height);\n    }\n  }\n\n  private _renderColorZone(zone: IColorZone): void {\n    this._ctx.fillStyle = zone.color;\n    this._ctx.fillRect(\n      /* x */ drawX[zone.position || 'full'],\n      /* y */ Math.round(\n        (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line\n        (zone.startBufferLine / this._bufferService.buffers.active.lines.length) - drawHeight[zone.position || 'full'] / 2\n      ),\n      /* w */ drawWidth[zone.position || 'full'],\n      /* h */ Math.round(\n        (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line\n        ((zone.endBufferLine - zone.startBufferLine) / this._bufferService.buffers.active.lines.length) + drawHeight[zone.position || 'full']\n      )\n    );\n  }\n\n  private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void {\n    this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions;\n    this._shouldUpdateAnchor = updateAnchor || this._shouldUpdateAnchor;\n    if (this._animationFrame !== undefined) {\n      return;\n    }\n    this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {\n      this._refreshDecorations();\n      this._animationFrame = undefined;\n    });\n  }\n}\n"
  },
  {
    "path": "src/browser/input/CompositionHelper.test.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { CompositionHelper } from 'browser/input/CompositionHelper';\nimport { MockRenderService } from 'browser/TestUtils.test';\nimport { MockCoreService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';\n\ndescribe('CompositionHelper', () => {\n  let compositionHelper: CompositionHelper;\n  let compositionView: HTMLElement;\n  let textarea: HTMLTextAreaElement;\n  let handledText: string;\n\n  beforeEach(() => {\n    compositionView = {\n      classList: {\n        add: () => {},\n        remove: () => {}\n      },\n      getBoundingClientRect: () => {\n        return { width: 0 };\n      },\n      style: {\n        left: 0,\n        top: 0\n      },\n      textContent: ''\n    } as any;\n    textarea = {\n      value: '',\n      style: {\n        left: 0,\n        top: 0\n      }\n    } as any;\n    const coreService = new MockCoreService();\n    coreService.triggerDataEvent = (text: string) => {\n      handledText += text;\n    };\n    handledText = '';\n    const bufferService = new MockBufferService(10, 5);\n    compositionHelper = new CompositionHelper(textarea, compositionView, bufferService, new MockOptionsService(), coreService, new MockRenderService());\n  });\n\n  describe('Input', () => {\n    it('Should insert simple characters', (done) => {\n      // First character 'ㅇ'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'ㅇ' });\n      textarea.value = 'ㅇ';\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionend();\n        setTimeout(() => { // wait for any textarea updates\n          assert.equal(handledText, 'ㅇ');\n          // Second character 'ㅇ'\n          compositionHelper.compositionstart();\n          compositionHelper.compositionupdate({ data: 'ㅇ' });\n          textarea.value = 'ㅇㅇ';\n          setTimeout(() => { // wait for any textarea updates\n            compositionHelper.compositionend();\n            setTimeout(() => { // wait for any textarea updates\n              assert.equal(handledText, 'ㅇㅇ');\n              done();\n            }, 0);\n          }, 0);\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert complex characters', (done) => {\n      // First character '앙'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'ㅇ' });\n      textarea.value = 'ㅇ';\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionupdate({ data: '아' });\n        textarea.value = '아';\n        setTimeout(() => { // wait for any textarea updates\n          compositionHelper.compositionupdate({ data: '앙' });\n          textarea.value = '앙';\n          setTimeout(() => { // wait for any textarea updates\n            compositionHelper.compositionend();\n            setTimeout(() => { // wait for any textarea updates\n              assert.equal(handledText, '앙');\n              // Second character '앙'\n              compositionHelper.compositionstart();\n              compositionHelper.compositionupdate({ data: 'ㅇ' });\n              textarea.value = '앙ㅇ';\n              setTimeout(() => { // wait for any textarea updates\n                compositionHelper.compositionupdate({ data: '아' });\n                textarea.value = '앙아';\n                setTimeout(() => { // wait for any textarea updates\n                  compositionHelper.compositionupdate({ data: '앙' });\n                  textarea.value = '앙앙';\n                  setTimeout(() => { // wait for any textarea updates\n                    compositionHelper.compositionend();\n                    setTimeout(() => { // wait for any textarea updates\n                      assert.equal(handledText, '앙앙');\n                      done();\n                    }, 0);\n                  }, 0);\n                }, 0);\n              }, 0);\n            }, 0);\n          }, 0);\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert complex characters that change with following character', (done) => {\n      // First character '아'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'ㅇ' });\n      textarea.value = 'ㅇ';\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionupdate({ data: '아' });\n        textarea.value = '아';\n        setTimeout(() => { // wait for any textarea updates\n          // Start second character '아' in first character\n          compositionHelper.compositionupdate({ data: '앙' });\n          textarea.value = '앙';\n          setTimeout(() => { // wait for any textarea updates\n            compositionHelper.compositionend();\n            compositionHelper.compositionstart();\n            compositionHelper.compositionupdate({ data: '아' });\n            textarea.value = '아아';\n            setTimeout(() => { // wait for any textarea updates\n              compositionHelper.compositionend();\n              setTimeout(() => { // wait for any textarea updates\n                assert.equal(handledText, '아아');\n                done();\n              }, 0);\n            }, 0);\n          }, 0);\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert multi-characters compositions', (done) => {\n      // First character 'だ'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'd' });\n      textarea.value = 'd';\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionupdate({ data: 'だ' });\n        textarea.value = 'だ';\n        setTimeout(() => { // wait for any textarea updates\n          // Second character 'あ'\n          compositionHelper.compositionupdate({ data: 'だあ' });\n          textarea.value = 'だあ';\n          setTimeout(() => { // wait for any textarea updates\n            compositionHelper.compositionend();\n            setTimeout(() => { // wait for any textarea updates\n              assert.equal(handledText, 'だあ');\n              done();\n            }, 0);\n          }, 0);\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => {\n      // First character 'だ'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'd' });\n      textarea.value = 'd';\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionupdate({ data: 'だ' });\n        textarea.value = 'だ';\n        setTimeout(() => { // wait for any textarea updates\n          // Second character 'ー'\n          compositionHelper.compositionupdate({ data: 'だー' });\n          textarea.value = 'だー';\n          setTimeout(() => { // wait for any textarea updates\n            // Convert to katakana 'ダー'\n            compositionHelper.compositionupdate({ data: 'ダー' });\n            textarea.value = 'ダー';\n            setTimeout(() => { // wait for any textarea updates\n              compositionHelper.compositionend();\n              setTimeout(() => { // wait for any textarea updates\n                assert.equal(handledText, 'ダー');\n                done();\n              }, 0);\n            }, 0);\n          }, 0);\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => {\n      // First character 'い'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'い' });\n      textarea.value = 'い';\n      setTimeout(() => { // wait for any textarea updates\n        // Second character 'ま'\n        compositionHelper.compositionupdate({ data: 'いm' });\n        textarea.value = 'いm';\n        setTimeout(() => { // wait for any textarea updates\n          compositionHelper.compositionupdate({ data: 'いま' });\n          textarea.value = 'いま';\n          setTimeout(() => { // wait for any textarea updates\n            // Convert to kanji '今'\n            compositionHelper.compositionupdate({ data: '今' });\n            textarea.value = '今';\n            setTimeout(() => { // wait for any textarea updates\n              compositionHelper.compositionend();\n              setTimeout(() => { // wait for any textarea updates\n                assert.equal(handledText, '今');\n                done();\n              }, 0);\n            }, 0);\n          }, 0);\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert non-composition characters input immediately after composition characters', (done) => {\n      // First character 'ㅇ'\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: 'ㅇ' });\n      textarea.value = 'ㅇ';\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionend();\n        // Second character '1' (a non-composition character)\n        textarea.value = 'ㅇ1';\n        setTimeout(() => { // wait for any textarea updates\n          assert.equal(handledText, 'ㅇ1');\n          done();\n        }, 0);\n      }, 0);\n    });\n\n    it('Should insert middle composition and subsequent input without appending existing trailing text', (done) => {\n      textarea.value = '一二';\n      // screenReaderMode keeps textarea content/selection for assistive technologies (eg. screen\n      // readers), so the caret can be moved within the textarea (eg. via arrow keys) before\n      // starting composition.\n      textarea.selectionStart = 1;\n      textarea.selectionEnd = 1;\n\n      compositionHelper.compositionstart();\n      compositionHelper.compositionupdate({ data: '一' });\n      textarea.value = '一一二';\n      // After the composed text is inserted, the caret typically moves to after it.\n      textarea.selectionStart = 2;\n      textarea.selectionEnd = 2;\n\n      setTimeout(() => { // wait for any textarea updates\n        compositionHelper.compositionend();\n        // Second character '1' (a non-composition character)\n        textarea.value = '一一1二';\n        setTimeout(() => { // wait for any textarea updates\n          assert.equal(handledText, '一1');\n          done();\n        }, 0);\n      }, 0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/browser/input/CompositionHelper.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderService } from 'browser/services/Services';\nimport { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';\nimport { C0 } from 'common/data/EscapeSequences';\n\ninterface IPosition {\n  start: number;\n  end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n  /**\n   * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n   * IME. This variable determines whether the compositionText should be displayed on the UI.\n   */\n  private _isComposing: boolean;\n  public get isComposing(): boolean { return this._isComposing; }\n\n  /**\n   * The position within the input textarea's value of the current composition.\n   */\n  private _compositionPosition: IPosition;\n\n  /**\n   * Text that existed after the composing range when composition started.\n   * This is used to avoid treating existing trailing text as new input.\n   */\n  private _compositionSuffix: string;\n\n  /**\n   * Whether a composition is in the process of being sent, setting this to false will cancel any\n   * in-progress composition.\n   */\n  private _isSendingComposition: boolean;\n\n  /**\n   * Data already sent due to keydown event.\n   */\n  private _dataAlreadySent: string;\n\n  /**\n   * The pending textarea change timer, if any.\n   */\n  private _textareaChangeTimer?: number;\n\n  constructor(\n    private readonly _textarea: HTMLTextAreaElement,\n    private readonly _compositionView: HTMLElement,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @IRenderService private readonly _renderService: IRenderService\n  ) {\n    this._isComposing = false;\n    this._isSendingComposition = false;\n    this._compositionPosition = { start: 0, end: 0 };\n    this._compositionSuffix = '';\n    this._dataAlreadySent = '';\n  }\n\n  /**\n   * Handles the compositionstart event, activating the composition view.\n   */\n  public compositionstart(): void {\n    this._isComposing = true;\n    // It's important to use the selection here instead of textarea length to avoid conflicts with\n    // screen reader mode\n    const start = this._textarea.selectionStart ?? this._textarea.value.length;\n    const end = this._textarea.selectionEnd ?? start;\n    this._compositionPosition.start = Math.min(start, end);\n    this._compositionPosition.end = Math.max(start, end);\n    this._compositionSuffix = this._textarea.value.substring(this._compositionPosition.end);\n    this._compositionView.textContent = '';\n    this._dataAlreadySent = '';\n    this._compositionView.classList.add('active');\n  }\n\n  /**\n   * Handles the compositionupdate event, updating the composition view.\n   * @param ev The event.\n   */\n  public compositionupdate(ev: Pick<CompositionEvent, 'data'>): void {\n    // Mark text as LTR, direction=rtl is used in CSS so the end of the text is followed for long\n    // compositions\n    this._compositionView.textContent = `\\u200E${ev.data}\\u200E`;\n    this.updateCompositionElements();\n    setTimeout(() => {\n      const end = this._textarea.selectionEnd ?? this._textarea.value.length;\n      this._compositionPosition.end = Math.max( this._compositionPosition.start, end);\n    }, 0);\n  }\n\n  /**\n   * Handles the compositionend event, hiding the composition view and sending the composition to\n   * the handler.\n   */\n  public compositionend(): void {\n    this._finalizeComposition(true);\n  }\n\n  /**\n   * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n   * @param ev The keydown event.\n   * @returns Whether the Terminal should continue processing the keydown event.\n   */\n  public keydown(ev: KeyboardEvent): boolean {\n    if (this._isComposing || this._isSendingComposition) {\n      if (ev.keyCode === 20 || ev.keyCode === 229) {\n        // 20 is CapsLock, 229 is Enter\n        // Continue composing if the keyCode is the \"composition character\"\n        return false;\n      }\n      if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n        // Continue composing if the keyCode is a modifier key\n        return false;\n      }\n      // Finish composition immediately. This is mainly here for the case where enter is\n      // pressed and the handler needs to be triggered before the command is executed.\n      this._finalizeComposition(false);\n    }\n\n    if (ev.keyCode === 229) {\n      // If the \"composition character\" is used but gets to this point it means a non-composition\n      // character (eg. numbers and punctuation) was pressed when the IME was active.\n      this._handleAnyTextareaChanges();\n      return false;\n    }\n\n    return true;\n  }\n\n  /**\n   * Finalizes the composition, resuming regular input actions. This is called when a composition\n   * is ending.\n   * @param waitForPropagation Whether to wait for events to propagate before sending\n   *   the input. This should be false if a non-composition keystroke is entered before the\n   *   compositionend event is triggered, such as enter, so that the composition is sent before\n   *   the command is executed.\n   */\n  private _finalizeComposition(waitForPropagation: boolean): void {\n    this._compositionView.classList.remove('active');\n    this._isComposing = false;\n\n    if (!waitForPropagation) {\n      // Cancel any delayed composition send requests and send the input immediately.\n      this._isSendingComposition = false;\n      const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);\n      this._coreService.triggerDataEvent(input, true);\n    } else {\n      // Make a deep copy of the composition position here as a new compositionstart event may\n      // fire before the setTimeout executes.\n      const currentCompositionPosition = {\n        start: this._compositionPosition.start,\n        end: this._compositionPosition.end\n      };\n      const currentCompositionSuffix = this._compositionSuffix;\n\n      // Since composition* events happen before the changes take place in the textarea on most\n      // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n      // complete. This ensures the correct character is retrieved.\n      // This solution was used because:\n      // - The compositionend event's data property is unreliable, at least on Chromium\n      // - The last compositionupdate event's data property does not always accurately describe\n      //   the character, a counter example being Korean where an ending consonsant can move to\n      //   the following character if the following input is a vowel.\n      this._isSendingComposition = true;\n      setTimeout(() => {\n        // Ensure that the input has not already been sent\n        if (this._isSendingComposition) {\n          this._isSendingComposition = false;\n          let input;\n          // Add length of data already sent due to keydown event,\n          // otherwise input characters can be duplicated. (Issue #3191)\n          currentCompositionPosition.start += this._dataAlreadySent.length;\n          if (this._isComposing) {\n            // Use the start position of the new composition to get the string\n            // if a new composition has started.\n            input = this._textarea.value.substring(currentCompositionPosition.start, this._compositionPosition.start);\n          } else {\n            // Keep support for non-composition characters typed immediately after composition end\n            // while avoiding re-sending the trailing text that was already present\n            // before composition started.\n            const value = this._textarea.value;\n            const valueEnd = currentCompositionSuffix.length > 0 && value.endsWith(currentCompositionSuffix)\n              ? value.length - currentCompositionSuffix.length\n              : value.length;\n            input = value.substring(currentCompositionPosition.start, Math.max(currentCompositionPosition.start, valueEnd));\n          }\n          if (input.length > 0) {\n            this._coreService.triggerDataEvent(input, true);\n          }\n        }\n      }, 0);\n    }\n  }\n\n  /**\n   * Apply any changes made to the textarea after the current event chain is allowed to complete.\n   * This should be called when not currently composing but a keydown event with the \"composition\n   * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n   * IME is active.\n   */\n  private _handleAnyTextareaChanges(): void {\n    if (this._textareaChangeTimer) {\n      return;\n    }\n    const oldValue = this._textarea.value;\n    this._textareaChangeTimer = window.setTimeout(() => {\n      this._textareaChangeTimer = undefined;\n      // Ignore if a composition has started since the timeout\n      if (!this._isComposing) {\n        const newValue = this._textarea.value;\n\n        const diff = newValue.replace(oldValue, '');\n\n        this._dataAlreadySent = diff;\n\n        if (newValue.length > oldValue.length) {\n          this._coreService.triggerDataEvent(diff, true);\n        } else if (newValue.length < oldValue.length) {\n          this._coreService.triggerDataEvent(`${C0.DEL}`, true);\n        } else if ((newValue.length === oldValue.length) && (newValue !== oldValue)) {\n          this._coreService.triggerDataEvent(newValue, true);\n        }\n\n      }\n    }, 0);\n  }\n\n  /**\n   * Positions the composition view on top of the cursor and the textarea just below it (so the\n   * IME helper dialog is positioned correctly).\n   * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n   *   necessary as the IME events across browsers are not consistently triggered.\n   */\n  public updateCompositionElements(dontRecurse?: boolean): void {\n    if (!this._isComposing) {\n      return;\n    }\n\n    if (this._bufferService.buffer.isCursorInViewport) {\n      const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);\n\n      const cellHeight = this._renderService.dimensions.css.cell.height;\n      const cursorTop = this._bufferService.buffer.y * this._renderService.dimensions.css.cell.height;\n      const cursorLeft = cursorX * this._renderService.dimensions.css.cell.width;\n\n      this._compositionView.style.left = cursorLeft + 'px';\n      this._compositionView.style.top = cursorTop + 'px';\n      this._compositionView.style.height = cellHeight + 'px';\n      this._compositionView.style.lineHeight = cellHeight + 'px';\n      this._compositionView.style.fontFamily = this._optionsService.rawOptions.fontFamily;\n      this._compositionView.style.fontSize = this._optionsService.rawOptions.fontSize + 'px';\n      // Limit the composition view width to the space between the cursor and\n      // the terminal's right edge, preventing it from overflowing the terminal.\n      const maxWidth = this._bufferService.cols * this._renderService.dimensions.css.cell.width - cursorLeft;\n      this._compositionView.style.maxWidth = maxWidth + 'px';\n      this._compositionView.style.overflow = 'hidden';\n      this._compositionView.style.direction = 'rtl';\n      // Sync the textarea to the exact position of the composition view so the IME knows where the\n      // text is.\n      const compositionViewBounds = this._compositionView.getBoundingClientRect();\n      this._textarea.style.left = cursorLeft + 'px';\n      this._textarea.style.top = cursorTop + 'px';\n      // Ensure the text area is at least 1x1, otherwise certain IMEs may break\n      this._textarea.style.width = Math.max(compositionViewBounds.width, 1) + 'px';\n      this._textarea.style.height = Math.max(compositionViewBounds.height, 1) + 'px';\n      this._textarea.style.lineHeight = compositionViewBounds.height + 'px';\n    }\n\n    if (!dontRecurse) {\n      setTimeout(() => this.updateCompositionElements(true), 0);\n    }\n  }\n}\n"
  },
  {
    "path": "src/browser/input/Mouse.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport jsdom = require('jsdom');\nimport { assert } from 'chai';\nimport { getCoords } from 'browser/input/Mouse';\n\nconst CHAR_WIDTH = 10;\nconst CHAR_HEIGHT = 20;\n\ndescribe('Mouse getCoords', () => {\n  let windowOverride: Pick<Window, 'getComputedStyle'>;\n  let document: Document;\n\n  beforeEach(() => {\n    windowOverride = {\n      getComputedStyle(): any {\n        return {\n          getPropertyValue: () => '0px'\n        } as Pick<CSSStyleDeclaration, 'getPropertyValue'>;\n      }\n    };\n    document = new jsdom.JSDOM('').window.document;\n  });\n\n  it('should return the cell that was clicked', () => {\n    let coords: [number, number] | undefined;\n    coords = getCoords(windowOverride, { clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);\n    assert.deepEqual(coords, [1, 1]);\n    coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);\n    assert.deepEqual(coords, [1, 1]);\n    coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);\n    assert.deepEqual(coords, [1, 2]);\n    coords = getCoords(windowOverride, { clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);\n    assert.deepEqual(coords, [2, 1]);\n  });\n\n  it('should ensure the coordinates are returned within the terminal bounds', () => {\n    let coords: [number, number] | undefined;\n    coords = getCoords(windowOverride, { clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);\n    assert.deepEqual(coords, [1, 1]);\n    // Event are double the cols/rows\n    coords = getCoords(windowOverride, { clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);\n    assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');\n  });\n});\n"
  },
  {
    "path": "src/browser/input/Mouse.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {\n  const rect = element.getBoundingClientRect();\n  const elementStyle = window.getComputedStyle(element);\n  const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'));\n  const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'));\n  return [\n    event.clientX - rect.left - leftPadding,\n    event.clientY - rect.top - topPadding\n  ];\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param window The window object the element belongs to.\n * @param event The mouse event.\n * @param element The terminal's container element.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param hasValidCharSize Whether there is a valid character size available.\n * @param cssCellWidth The cell width device pixel render dimensions.\n * @param cssCellHeight The cell height device pixel render dimensions.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\nexport function getCoords(window: Pick<Window, 'getComputedStyle'>, event: Pick<MouseEvent, 'clientX' | 'clientY'>, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, cssCellWidth: number, cssCellHeight: number, isSelection?: boolean): [number, number] | undefined {\n  // Coordinates cannot be measured if there are no valid\n  if (!hasValidCharSize) {\n    return undefined;\n  }\n\n  const coords = getCoordsRelativeToElement(window, event, element);\n  if (!coords) {\n    return undefined;\n  }\n\n  coords[0] = Math.ceil((coords[0] + (isSelection ? cssCellWidth / 2 : 0)) / cssCellWidth);\n  coords[1] = Math.ceil(coords[1] / cssCellHeight);\n\n  // Ensure coordinates are within the terminal viewport. Note that selections\n  // need an addition point of precision to cover the end point (as characters\n  // cover half of one char and half of the next).\n  coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));\n  coords[1] = Math.min(Math.max(coords[1], 1), rowCount);\n\n  return coords;\n}\n"
  },
  {
    "path": "src/browser/input/MoveToCell.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { IBufferService } from 'common/services/Services';\nimport { MockBufferService } from 'common/TestUtils.test';\nimport { moveToCellSequence } from './MoveToCell';\n\ndescribe('MoveToCell', () => {\n  let bufferService: IBufferService;\n\n  beforeEach(() => {\n    bufferService = new MockBufferService(5, 5);\n    bufferService.buffer.x = 3;\n    bufferService.buffer.y = 3;\n  });\n\n  describe('normal buffer', () => {\n    it('should use the right directional escape sequences', () => {\n      assert.equal(moveToCellSequence(1, 3, bufferService, false), '\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(2, 3, bufferService, false), '\\x1b[D');\n      assert.equal(moveToCellSequence(4, 3, bufferService, false), '\\x1b[C');\n      assert.equal(moveToCellSequence(5, 3, bufferService, false), '\\x1b[C\\x1b[C');\n    });\n    it('should wrap around entire row instead of doing up and down when the Y value differs', () => {\n      assert.equal(moveToCellSequence(1, 1, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(2, 1, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(3, 1, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(4, 1, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(5, 1, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(1, 2, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(2, 2, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(3, 2, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(4, 2, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(5, 2, bufferService, false), '\\x1b[D\\x1b[D\\x1b[D');\n      assert.equal(moveToCellSequence(1, 4, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(2, 4, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(3, 4, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(4, 4, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(5, 4, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(1, 5, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(2, 5, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(3, 5, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(4, 5, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n      assert.equal(moveToCellSequence(5, 5, bufferService, false), '\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C\\x1b[C');\n    });\n    it('should use the correct character for application cursor', () => {\n      assert.equal(moveToCellSequence(3, 1, bufferService, true), '\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD');\n      assert.equal(moveToCellSequence(3, 2, bufferService, true), '\\x1bOD\\x1bOD\\x1bOD\\x1bOD\\x1bOD');\n      assert.equal(moveToCellSequence(2, 3, bufferService, true), '\\x1bOD');\n      assert.equal(moveToCellSequence(4, 3, bufferService, true), '\\x1bOC');\n      assert.equal(moveToCellSequence(3, 4, bufferService, true), '\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC');\n      assert.equal(moveToCellSequence(3, 5, bufferService, true), '\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC\\x1bOC');\n    });\n  });\n\n  describe('alt buffer', () => {\n    beforeEach(() => {\n      bufferService.buffers.activateAltBuffer();\n      bufferService.buffer.x = 3;\n      bufferService.buffer.y = 3;\n    });\n\n    it('should move the cursor across rows', () => {\n      assert.equal(moveToCellSequence(4, 4, bufferService, false), '\\x1b[B\\x1b[C');\n    });\n  });\n});\n"
  },
  {
    "path": "src/browser/input/MoveToCell.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { C0 } from 'common/data/EscapeSequences';\nimport { IBufferService } from 'common/services/Services';\n\nconst enum Direction {\n  UP = 'A',\n  DOWN = 'B',\n  RIGHT = 'C',\n  LEFT = 'D'\n}\n\n/**\n * Concatenates all the arrow sequences together.\n * Resets the starting row to an unwrapped row, moves to the requested row,\n * then moves to requested col.\n */\nexport function moveToCellSequence(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {\n  const startX = bufferService.buffer.x;\n  const startY = bufferService.buffer.y;\n\n  // The alt buffer should try to navigate between rows\n  if (!bufferService.buffer.hasScrollback) {\n    return resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) +\n      moveToRequestedRow(startY, targetY, bufferService, applicationCursor) +\n      moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor);\n  }\n\n  // Only move horizontally for the normal buffer\n  let direction;\n  if (startY === targetY) {\n    direction = startX > targetX ? Direction.LEFT : Direction.RIGHT;\n    return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor));\n  }\n  direction = startY > targetY ? Direction.LEFT : Direction.RIGHT;\n  const rowDifference = Math.abs(startY - targetY);\n  const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) +\n    (rowDifference - 1) * bufferService.cols + 1 /* wrap around 1 row */ +\n    colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService);\n  return repeat(cellsToMove, sequence(direction, applicationCursor));\n}\n\n/**\n * Find the number of cols from a row beginning to a col.\n */\nfunction colsFromRowBeginning(currX: number, bufferService: IBufferService): number {\n  return currX - 1;\n}\n\n/**\n * Find the number of cols from a col to row end.\n */\nfunction colsFromRowEnd(currX: number, bufferService: IBufferService): number {\n  return bufferService.cols - currX;\n}\n\n/**\n * If the initial position of the cursor is on a row that is wrapped, move the\n * cursor up to the first row that is not wrapped to have accurate vertical\n * positioning.\n */\nfunction resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {\n  if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) {\n    return '';\n  }\n  return repeat(bufferLine(\n    startX, startY, startX,\n    startY - wrappedRowsForRow(startY, bufferService), false, bufferService\n  ).length, sequence(Direction.LEFT, applicationCursor));\n}\n\n/**\n * Using the reset starting and ending row, move to the requested row,\n * ignoring wrapped rows\n */\nfunction moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {\n  const startRow = startY - wrappedRowsForRow(startY, bufferService);\n  const endRow = targetY - wrappedRowsForRow(targetY, bufferService);\n\n  const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService);\n\n  return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor));\n}\n\n/**\n * Move to the requested col on the ending row\n */\nfunction moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {\n  let startRow;\n  if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {\n    startRow = targetY - wrappedRowsForRow(targetY, bufferService);\n  } else {\n    startRow = startY;\n  }\n\n  const endRow = targetY;\n  const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);\n\n  return repeat(bufferLine(\n    startX, startRow, targetX, endRow,\n    direction === Direction.RIGHT, bufferService\n  ).length, sequence(direction, applicationCursor));\n}\n\n/**\n * Utility functions\n */\n\n/**\n * Calculates the number of wrapped rows between the unwrapped starting and\n * ending rows. These rows need to ignored since the cursor skips over them.\n */\nfunction wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number {\n  let wrappedRows = 0;\n  const startRow = startY - wrappedRowsForRow(startY, bufferService);\n  const endRow = targetY - wrappedRowsForRow(targetY, bufferService);\n\n  for (let i = 0; i < Math.abs(startRow - endRow); i++) {\n    const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1;\n    const line = bufferService.buffer.lines.get(startRow + (direction * i));\n    if (line?.isWrapped) {\n      wrappedRows++;\n    }\n  }\n\n  return wrappedRows;\n}\n\n/**\n * Calculates the number of wrapped rows that make up a given row.\n * @param currentRow The row to determine how many wrapped rows make it up\n */\nfunction wrappedRowsForRow(currentRow: number, bufferService: IBufferService): number {\n  let rowCount = 0;\n  let line = bufferService.buffer.lines.get(currentRow);\n  let lineWraps = line?.isWrapped;\n\n  while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) {\n    rowCount++;\n    line = bufferService.buffer.lines.get(--currentRow);\n    lineWraps = line?.isWrapped;\n  }\n\n  return rowCount;\n}\n\n/**\n * Direction determiners\n */\n\n/**\n * Determines if the right or left arrow is needed\n */\nfunction horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {\n  let startRow;\n  if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {\n    startRow = targetY - wrappedRowsForRow(targetY, bufferService);\n  } else {\n    startRow = startY;\n  }\n\n  if ((startX < targetX &&\n    startRow <= targetY) || // down/right or same y/right\n    (startX >= targetX &&\n    startRow < targetY)) {  // down/left or same y/left\n    return Direction.RIGHT;\n  }\n  return Direction.LEFT;\n}\n\n/**\n * Determines if the up or down arrow is needed\n */\nfunction verticalDirection(startY: number, targetY: number): Direction {\n  return startY > targetY ? Direction.UP : Direction.DOWN;\n}\n\n/**\n * Constructs the string of chars in the buffer from a starting row and col\n * to an ending row and col\n * @param startCol The starting column position\n * @param startRow The starting row position\n * @param endCol The ending column position\n * @param endRow The ending row position\n * @param forward Direction to move\n */\nfunction bufferLine(\n  startCol: number,\n  startRow: number,\n  endCol: number,\n  endRow: number,\n  forward: boolean,\n  bufferService: IBufferService\n): string {\n  let currentCol = startCol;\n  let currentRow = startRow;\n  let bufferStr = '';\n\n  while ((currentCol !== endCol || currentRow !== endRow) &&\n         currentRow >= 0 &&\n         currentRow < bufferService.buffer.lines.length) {\n    currentCol += forward ? 1 : -1;\n\n    if (forward && currentCol > bufferService.cols - 1) {\n      bufferStr += bufferService.buffer.translateBufferLineToString(\n        currentRow, false, startCol, currentCol\n      );\n      currentCol = 0;\n      startCol = 0;\n      currentRow++;\n    } else if (!forward && currentCol < 0) {\n      bufferStr += bufferService.buffer.translateBufferLineToString(\n        currentRow, false, 0, startCol + 1\n      );\n      currentCol = bufferService.cols - 1;\n      startCol = currentCol;\n      currentRow--;\n    }\n  }\n\n  return bufferStr + bufferService.buffer.translateBufferLineToString(\n    currentRow, false, startCol, currentCol\n  );\n}\n\n/**\n * Constructs the escape sequence for clicking an arrow\n * @param direction The direction to move\n */\nfunction sequence(direction: Direction, applicationCursor: boolean): string {\n  const mod =  applicationCursor ? 'O' : '[';\n  return C0.ESC + mod + direction;\n}\n\n/**\n * Returns a string repeated a given number of times\n * Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat\n * @param count The number of times to repeat the string\n * @param str The string that is to be repeated\n */\nfunction repeat(count: number, str: string): string {\n  count = Math.floor(count);\n  let rpt = '';\n  for (let i = 0; i < count; i++) {\n    rpt += str;\n  }\n  return rpt;\n}\n"
  },
  {
    "path": "src/browser/public/Terminal.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as Strings from 'browser/LocalizableStrings';\nimport { CoreBrowserTerminal as TerminalCore } from 'browser/CoreBrowserTerminal';\nimport { IBufferRange, ITerminal } from 'browser/Types';\nimport { Disposable } from 'common/Lifecycle';\nimport { ITerminalOptions } from 'common/Types';\nimport { AddonManager } from 'common/public/AddonManager';\nimport { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';\nimport { ParserApi } from 'common/public/ParserApi';\nimport { UnicodeApi } from 'common/public/UnicodeApi';\nimport { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, IRenderDimensions, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm';\nimport type { IEvent } from 'common/Event';\n\n/**\n * The set of options that only have an effect when set in the Terminal constructor.\n */\nconst CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];\n\nlet $value = 0;\n\nexport class Terminal extends Disposable implements ITerminalApi {\n  private _core: ITerminal;\n  private _addonManager: AddonManager;\n  private _parser: IParser | undefined;\n  private _buffer: BufferNamespaceApi | undefined;\n  private _publicOptions: Required<ITerminalOptions>;\n\n  constructor(options?: ITerminalOptions & ITerminalInitOnlyOptions) {\n    super();\n\n    this._core = this._register(new TerminalCore(options));\n    this._addonManager = this._register(new AddonManager());\n\n    this._publicOptions = { ... this._core.options };\n    const getter = (propName: string): any => {\n      return this._core.options[propName];\n    };\n    const setter = (propName: string, value: any): void => {\n      this._checkReadonlyOptions(propName);\n      this._core.options[propName] = value;\n    };\n\n    for (const propName in this._core.options) {\n      const desc = {\n        get: getter.bind(this, propName),\n        set: setter.bind(this, propName)\n      };\n      Object.defineProperty(this._publicOptions, propName, desc);\n    }\n  }\n\n  private _checkReadonlyOptions(propName: string): void {\n    // Throw an error if any constructor only option is modified\n    // from terminal.options\n    // Modifications from anywhere else are allowed\n    if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {\n      throw new Error(`Option \"${propName}\" can only be set in the constructor`);\n    }\n  }\n\n  private _checkProposedApi(): void {\n    if (!this._core.optionsService.rawOptions.allowProposedApi) {\n      throw new Error('You must set the allowProposedApi option to true to use proposed API');\n    }\n  }\n\n  public get onBell(): IEvent<void> { return this._core.onBell; }\n  public get onBinary(): IEvent<string> { return this._core.onBinary; }\n  public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }\n  public get onData(): IEvent<string> { return this._core.onData; }\n  public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }\n  public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }\n  public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }\n  public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }\n  public get onScroll(): IEvent<number> { return this._core.onScroll; }\n  public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }\n  public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }\n  public get onWriteParsed(): IEvent<void> { return this._core.onWriteParsed; }\n  public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._core.onDimensionsChange; }\n\n  public get element(): HTMLElement | undefined { return this._core.element; }\n  public get screenElement(): HTMLElement | undefined { return this._core.screenElement; }\n  public get parser(): IParser {\n    return this._parser ??= new ParserApi(this._core);\n  }\n  public get unicode(): IUnicodeHandling {\n    this._checkProposedApi();\n    return new UnicodeApi(this._core);\n  }\n  public get textarea(): HTMLTextAreaElement | undefined { return this._core.textarea; }\n  public get rows(): number { return this._core.rows; }\n  public get cols(): number { return this._core.cols; }\n  public get buffer(): IBufferNamespaceApi {\n    return this._buffer ??= this._register(new BufferNamespaceApi(this._core));\n  }\n  public get markers(): ReadonlyArray<IMarker> {\n    return this._core.markers;\n  }\n  public get modes(): IModes {\n    const m = this._core.coreService.decPrivateModes;\n    let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';\n    switch (this._core.mouseStateService.activeProtocol) {\n      case 'X10': mouseTrackingMode = 'x10'; break;\n      case 'VT200': mouseTrackingMode = 'vt200'; break;\n      case 'DRAG': mouseTrackingMode = 'drag'; break;\n      case 'ANY': mouseTrackingMode = 'any'; break;\n    }\n    return {\n      applicationCursorKeysMode: m.applicationCursorKeys,\n      applicationKeypadMode: m.applicationKeypad,\n      bracketedPasteMode: m.bracketedPasteMode,\n      insertMode: this._core.coreService.modes.insertMode,\n      mouseTrackingMode: mouseTrackingMode,\n      originMode: m.origin,\n      reverseWraparoundMode: m.reverseWraparound,\n      sendFocusMode: m.sendFocus,\n      showCursor: !this._core.coreService.isCursorHidden,\n      synchronizedOutputMode: m.synchronizedOutput,\n      win32InputMode: m.win32InputMode,\n      wraparoundMode: m.wraparound\n    };\n  }\n  public get dimensions(): IRenderDimensions | undefined {\n    return this._core.dimensions;\n  }\n  public get options(): Required<ITerminalOptions> {\n    return this._publicOptions;\n  }\n  public set options(options: ITerminalOptions) {\n    for (const propName in options) {\n      this._publicOptions[propName] = options[propName];\n    }\n  }\n  public blur(): void {\n    this._core.blur();\n  }\n  public focus(): void {\n    this._core.focus();\n  }\n  public input(data: string, wasUserInput: boolean = true): void {\n    this._core.input(data, wasUserInput);\n  }\n  public resize(columns: number, rows: number): void {\n    this._verifyIntegers(columns, rows);\n    this._core.resize(columns, rows);\n  }\n  public open(parent: HTMLElement): void {\n    this._core.open(parent);\n  }\n  public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {\n    this._core.attachCustomKeyEventHandler(customKeyEventHandler);\n  }\n  public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void {\n    this._core.attachCustomWheelEventHandler(customWheelEventHandler);\n  }\n  public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {\n    return this._core.registerLinkProvider(linkProvider);\n  }\n  public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {\n    return this._core.registerCharacterJoiner(handler);\n  }\n  public deregisterCharacterJoiner(joinerId: number): void {\n    this._core.deregisterCharacterJoiner(joinerId);\n  }\n  public registerMarker(cursorYOffset: number = 0): IMarker {\n    this._verifyIntegers(cursorYOffset);\n    return this._core.registerMarker(cursorYOffset);\n  }\n  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {\n    this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0);\n    return this._core.registerDecoration(decorationOptions);\n  }\n  public hasSelection(): boolean {\n    return this._core.hasSelection();\n  }\n  public select(column: number, row: number, length: number): void {\n    this._verifyIntegers(column, row, length);\n    this._core.select(column, row, length);\n  }\n  public getSelection(): string {\n    return this._core.getSelection();\n  }\n  public getSelectionPosition(): IBufferRange | undefined {\n    return this._core.getSelectionPosition();\n  }\n  public clearSelection(): void {\n    this._core.clearSelection();\n  }\n  public selectAll(): void {\n    this._core.selectAll();\n  }\n  public selectLines(start: number, end: number): void {\n    this._verifyIntegers(start, end);\n    this._core.selectLines(start, end);\n  }\n  public dispose(): void {\n    super.dispose();\n  }\n  public scrollLines(amount: number): void {\n    this._verifyIntegers(amount);\n    this._core.scrollLines(amount);\n  }\n  public scrollPages(pageCount: number): void {\n    this._verifyIntegers(pageCount);\n    this._core.scrollPages(pageCount);\n  }\n  public scrollToTop(): void {\n    this._core.scrollToTop();\n  }\n  public scrollToBottom(): void {\n    this._core.scrollToBottom();\n  }\n  public scrollToLine(line: number): void {\n    this._verifyIntegers(line);\n    this._core.scrollToLine(line);\n  }\n  public clear(): void {\n    this._core.clear();\n  }\n  public write(data: string | Uint8Array, callback?: () => void): void {\n    this._core.write(data, callback);\n  }\n  public writeln(data: string | Uint8Array, callback?: () => void): void {\n    this._core.write(data);\n    this._core.write('\\r\\n', callback);\n  }\n  public paste(data: string): void {\n    this._core.paste(data);\n  }\n  public refresh(start: number, end: number): void {\n    this._verifyIntegers(start, end);\n    this._core.refresh(start, end);\n  }\n  public reset(): void {\n    this._core.reset();\n  }\n  public clearTextureAtlas(): void {\n    this._core.clearTextureAtlas();\n  }\n  public loadAddon(addon: ITerminalAddon): void {\n    this._addonManager.loadAddon(this, addon);\n  }\n  public static get strings(): ILocalizableStrings {\n    // A wrapper is required here because esbuild prevents setting an `export let`\n    return {\n      get promptLabel(): string { return Strings.promptLabel.get(); },\n      set promptLabel(value: string) { Strings.promptLabel.set(value); },\n      get tooMuchOutput(): string { return Strings.tooMuchOutput.get(); },\n      set tooMuchOutput(value: string) { Strings.tooMuchOutput.set(value); }\n    };\n  }\n\n  private _verifyIntegers(...values: number[]): void {\n    for ($value of values) {\n      if ($value === Infinity || isNaN($value) || $value % 1 !== 0) {\n        throw new Error('This API only accepts integers');\n      }\n    }\n  }\n\n  private _verifyPositiveIntegers(...values: number[]): void {\n    for ($value of values) {\n      if ($value && ($value === Infinity || isNaN($value) || $value % 1 !== 0 || $value < 0)) {\n        throw new Error('This API only accepts positive integers');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/browser/renderer/dom/DomRenderer.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererRowFactory';\nimport { WidthCache } from 'browser/renderer/dom/WidthCache';\nimport { INVERTED_DEFAULT_COLOR, RendererConstants } from 'browser/renderer/shared/Constants';\nimport { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';\nimport { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';\nimport { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';\nimport { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';\nimport { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';\nimport { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';\nimport { color } from 'common/Color';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IBufferService, ICoreService, IInstantiationService, IOptionsService } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\nimport { addDisposableListener } from 'browser/Dom';\n\n\nconst TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';\nconst ROW_CONTAINER_CLASS = 'xterm-rows';\nconst FG_CLASS_PREFIX = 'xterm-fg-';\nconst BG_CLASS_PREFIX = 'xterm-bg-';\nconst FOCUS_CLASS = 'xterm-focus';\nconst SELECTION_CLASS = 'xterm-selection';\nconst CURSOR_BLINK_IDLE_CLASS = 'xterm-cursor-blink-idle';\n\nlet nextTerminalId = 1;\n\n/**\n * The standard renderer and fallback for when the webgl addon is slow. This is not meant to be\n * particularly fast and will even lack some features such as custom glyphs, hoever this is more\n * reliable as webgl may not work on some machines.\n */\nexport class DomRenderer extends Disposable implements IRenderer {\n  private _rowFactory: DomRendererRowFactory;\n  private _terminalClass: number = nextTerminalId++;\n\n  private _themeStyleElement!: HTMLStyleElement;\n  private _dimensionsStyleElement!: HTMLStyleElement;\n  private _rowContainer: HTMLElement;\n  private _rowElements: HTMLElement[] = [];\n  private _selectionContainer: HTMLElement;\n  private _widthCache: WidthCache;\n  private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();\n  private _lastSelectionStart: [number, number] | undefined;\n  private _lastSelectionEnd: [number, number] | undefined;\n  private _lastSelectionColumnMode: boolean = false;\n  private _cursorBlinkStateManager: CursorBlinkStateManager;\n  private _textBlinkStateManager: TextBlinkStateManager;\n  private _rowHasBlinkingCells: boolean[] = [];\n  private _rowHasBlinkingCellsCount: number = 0;\n\n  public dimensions: IRenderDimensions;\n\n  private readonly _onRequestRedraw = this._register(new Emitter<IRequestRedrawEvent>());\n  public readonly onRequestRedraw = this._onRequestRedraw.event;\n\n  constructor(\n    private readonly _terminal: ITerminal,\n    private readonly _document: Document,\n    private readonly _element: HTMLElement,\n    private readonly _screenElement: HTMLElement,\n    private readonly _viewportElement: HTMLElement,\n    private readonly _helperContainer: HTMLElement,\n    private readonly _linkifier2: ILinkifier2,\n    @IInstantiationService instantiationService: IInstantiationService,\n    @ICharSizeService private readonly _charSizeService: ICharSizeService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n    @IThemeService private readonly _themeService: IThemeService\n  ) {\n    super();\n    this._rowContainer = this._document.createElement('div');\n    this._rowContainer.classList.add(ROW_CONTAINER_CLASS);\n    this._rowContainer.style.lineHeight = 'normal';\n    this._rowContainer.setAttribute('aria-hidden', 'true');\n    this._refreshRowElements(this._bufferService.cols, this._bufferService.rows);\n    this._selectionContainer = this._document.createElement('div');\n    this._selectionContainer.classList.add(SELECTION_CLASS);\n    this._selectionContainer.setAttribute('aria-hidden', 'true');\n\n    this.dimensions = createRenderDimensions();\n    this._updateDimensions();\n    this._register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));\n\n    this._register(this._themeService.onChangeColors(e => this._injectCss(e)));\n    this._injectCss(this._themeService.colors);\n\n    this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document);\n\n    this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);\n    this._screenElement.appendChild(this._rowContainer);\n    this._screenElement.appendChild(this._selectionContainer);\n\n    this._register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e)));\n    this._register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e)));\n\n    this._cursorBlinkStateManager = new CursorBlinkStateManager(this._rowContainer, this._coreBrowserService);\n    this._register(addDisposableListener(this._document, 'mousedown', () => this._cursorBlinkStateManager.restartBlinkAnimation()));\n    this._register(toDisposable(() => this._cursorBlinkStateManager.dispose()));\n    this._textBlinkStateManager = this._register(new TextBlinkStateManager(\n      () => this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }),\n      this._coreBrowserService,\n      this._optionsService\n    ));\n\n    this._register(toDisposable(() => {\n      this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);\n\n      // Outside influences such as React unmounts may manipulate the DOM before our disposal.\n      // https://github.com/xtermjs/xterm.js/issues/2960\n      this._rowContainer.remove();\n      this._selectionContainer.remove();\n      this._widthCache.dispose();\n      this._themeStyleElement.remove();\n      this._dimensionsStyleElement.remove();\n    }));\n\n    this._widthCache = new WidthCache();\n    this._widthCache.setFont(\n      this._optionsService.rawOptions.fontFamily,\n      this._optionsService.rawOptions.fontSize,\n      this._optionsService.rawOptions.fontWeight,\n      this._optionsService.rawOptions.fontWeightBold\n    );\n    this._setDefaultSpacing();\n  }\n\n  private _updateDimensions(): void {\n    const dpr = this._coreBrowserService.dpr;\n    this.dimensions.device.char.width = this._charSizeService.width * dpr;\n    this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr);\n    this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);\n    this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);\n    this.dimensions.device.char.left = 0;\n    this.dimensions.device.char.top = 0;\n    this.dimensions.device.canvas.width = this.dimensions.device.cell.width * this._bufferService.cols;\n    this.dimensions.device.canvas.height = this.dimensions.device.cell.height * this._bufferService.rows;\n    this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr);\n    this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr);\n    this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols;\n    this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows;\n\n    for (const element of this._rowElements) {\n      element.style.width = `${this.dimensions.css.canvas.width}px`;\n      element.style.height = `${this.dimensions.css.cell.height}px`;\n      element.style.lineHeight = `${this.dimensions.css.cell.height}px`;\n      // Make sure rows don't overflow onto following row\n      element.style.overflow = 'hidden';\n    }\n\n    if (!this._dimensionsStyleElement) {\n      this._dimensionsStyleElement = this._document.createElement('style');\n      this._screenElement.appendChild(this._dimensionsStyleElement);\n    }\n\n    const styles =\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +\n      ` display: inline-block;` +   // TODO: find workaround for inline-block (creates ~20% render penalty)\n      ` height: 100%;` +\n      ` vertical-align: top;` +\n      `}`;\n\n    this._dimensionsStyleElement.textContent = styles;\n\n    this._selectionContainer.style.height = this._viewportElement.style.height;\n    this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`;\n    this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`;\n  }\n\n  private _injectCss(colors: ReadonlyColorSet): void {\n    if (!this._themeStyleElement) {\n      this._themeStyleElement = this._document.createElement('style');\n      this._screenElement.appendChild(this._themeStyleElement);\n    }\n\n    // Base CSS\n    let styles =\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +\n      // Disabling pointer events circumvents a browser behavior that prevents `click` events from\n      // being delivered if the target element is replaced during the click. This happened due to\n      // refresh() being called during the mousedown handler to start a selection.\n      ` pointer-events: none;` +\n      ` color: ${colors.foreground.css};` +\n      `}`;\n    styles +=\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS}, ${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +\n      ` font-family: ${this._optionsService.rawOptions.fontFamily};` +\n      ` font-size: ${this._optionsService.rawOptions.fontSize}px;` +\n      ` font-kerning: none;` +\n      ` white-space: pre` +\n      `}`;\n    styles +=\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .xterm-dim {` +\n      ` color: ${color.multiplyOpacity(colors.foreground, 0.5).css};` +\n      `}`;\n    // Text styles\n    styles +=\n      `${this._terminalSelector} span:not(.${RowCss.BOLD_CLASS}) {` +\n      ` font-weight: ${this._optionsService.rawOptions.fontWeight};` +\n      `}` +\n      `${this._terminalSelector} span.${RowCss.BOLD_CLASS} {` +\n      ` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` +\n      `}` +\n      `${this._terminalSelector} span.${RowCss.ITALIC_CLASS} {` +\n      ` font-style: italic;` +\n      `}` +\n      `${this._terminalSelector} span.${RowCss.BLINK_HIDDEN_CLASS} {` +\n      ` visibility: hidden;` +\n      `}`;\n    // Blink animation\n    const blinkAnimationUnderlineId = `blink_underline_${this._terminalClass}`;\n    const blinkAnimationBarId = `blink_bar_${this._terminalClass}`;\n    const blinkAnimationBlockId = `blink_block_${this._terminalClass}`;\n    styles +=\n      `@keyframes ${blinkAnimationUnderlineId} {` +\n      ` 50% {` +\n      `  border-bottom-style: hidden;` +\n      ` }` +\n      `}`;\n    styles +=\n      `@keyframes ${blinkAnimationBarId} {` +\n      ` 50% {` +\n      `  box-shadow: none;` +\n      ` }` +\n      `}`;\n    styles +=\n      `@keyframes ${blinkAnimationBlockId} {` +\n      ` 0% {` +\n      `  background-color: ${colors.cursor.css};` +\n      `  color: ${colors.cursorAccent.css};` +\n      ` }` +\n      ` 50% {` +\n      `  background-color: inherit;` +\n      `  color: ${colors.cursor.css};` +\n      ` }` +\n      `}`;\n    // Cursor\n    styles +=\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` +\n      ` animation: ${blinkAnimationUnderlineId} 1s step-end infinite;` +\n      `}` +\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` +\n      ` animation: ${blinkAnimationBarId} 1s step-end infinite;` +\n      `}` +\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +\n      ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` +\n      `}` +\n      // Disable cursor blinking when idle\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` +\n      ` animation: none !important;` +\n      `}` +\n      // !important helps fix an issue where the cursor will not render on top of the selection,\n      // however it's very hard to fix this issue and retain the blink animation without the use of\n      // !important. So this edge case fails when cursor blink is on.\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +\n      ` background-color: ${colors.cursor.css};` +\n      ` color: ${colors.cursorAccent.css};` +\n      `}` +\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS}:not(.${RowCss.CURSOR_BLINK_CLASS}) {` +\n      ` background-color: ${colors.cursor.css} !important;` +\n      ` color: ${colors.cursorAccent.css} !important;` +\n      `}` +\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` +\n      ` outline: 1px solid ${colors.cursor.css};` +\n      ` outline-offset: -1px;` +\n      `}` +\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` +\n      ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` +\n      `}` +\n      `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` +\n      ` border-bottom: 1px ${colors.cursor.css};` +\n      ` border-bottom-style: solid;` +\n      ` height: calc(100% - 1px);` +\n      `}`;\n    // Selection\n    styles +=\n      `${this._terminalSelector} .${SELECTION_CLASS} {` +\n      ` position: absolute;` +\n      ` top: 0;` +\n      ` left: 0;` +\n      ` z-index: 1;` +\n      ` pointer-events: none;` +\n      `}` +\n      `${this._terminalSelector}.focus .${SELECTION_CLASS} div {` +\n      ` position: absolute;` +\n      ` background-color: ${colors.selectionBackgroundOpaque.css};` +\n      `}` +\n      `${this._terminalSelector} .${SELECTION_CLASS} div {` +\n      ` position: absolute;` +\n      ` background-color: ${colors.selectionInactiveBackgroundOpaque.css};` +\n      `}`;\n    // Colors\n    for (const [i, c] of colors.ansi.entries()) {\n      styles +=\n        `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +\n        `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` +\n        `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;\n    }\n    styles +=\n      `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` +\n      `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` +\n      `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`;\n\n    this._themeStyleElement.textContent = styles;\n  }\n\n  /**\n   * default letter spacing\n   * Due to rounding issues in dimensions dpr calc glyph might render\n   * slightly too wide or too narrow. The method corrects the stacking offsets\n   * by applying a default letter-spacing for all chars.\n   * The value gets passed to the row factory to avoid setting this value again\n   * (render speedup is roughly 10%).\n   */\n  private _setDefaultSpacing(): void {\n    // measure same char as in CharSizeService to get the base deviation\n    const spacing = this.dimensions.css.cell.width - this._widthCache.get('W', false, false);\n    this._rowContainer.style.letterSpacing = `${spacing}px`;\n    this._rowFactory.defaultSpacing = spacing;\n  }\n\n  public handleDevicePixelRatioChange(): void {\n    this._updateDimensions();\n    this._widthCache.clear();\n    this._setDefaultSpacing();\n  }\n\n  private _refreshRowElements(cols: number, rows: number): void {\n    // Add missing elements\n    for (let i = this._rowElements.length; i <= rows; i++) {\n      const row = this._document.createElement('div');\n      this._rowContainer.appendChild(row);\n      this._rowElements.push(row);\n      this._rowHasBlinkingCells.push(false);\n    }\n    // Remove excess elements\n    while (this._rowElements.length > rows) {\n      this._rowContainer.removeChild(this._rowElements.pop()!);\n      if (this._rowHasBlinkingCells.pop()) {\n        this._rowHasBlinkingCellsCount--;\n      }\n    }\n  }\n\n  public handleResize(cols: number, rows: number): void {\n    this._refreshRowElements(cols, rows);\n    this._updateDimensions();\n    this.handleSelectionChanged(this._selectionRenderModel.selectionStart, this._selectionRenderModel.selectionEnd, this._selectionRenderModel.columnSelectMode);\n  }\n\n  public handleCharSizeChanged(): void {\n    this._updateDimensions();\n    this._widthCache.clear();\n    this._setDefaultSpacing();\n  }\n\n  public handleBlur(): void {\n    this._rowContainer.classList.remove(FOCUS_CLASS);\n    this._cursorBlinkStateManager.pause();\n    this.renderRows(0, this._bufferService.rows - 1);\n  }\n\n  public handleFocus(): void {\n    this._rowContainer.classList.add(FOCUS_CLASS);\n    this._cursorBlinkStateManager.resume();\n    this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);\n  }\n\n  public handleViewportVisibilityChange(isVisible: boolean): void {\n    this._textBlinkStateManager.setViewportVisible(isVisible);\n  }\n\n  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {\n    const rows = this._bufferService.rows;\n\n    // Remove all selections\n    this._selectionContainer.replaceChildren();\n    this._rowFactory.handleSelectionChanged(start, end, columnSelectMode);\n\n    // Determine old selection viewport band\n    let oldViewportStart = 0;\n    let oldViewportEnd = -1;\n    if (this._lastSelectionStart && this._lastSelectionEnd) {\n      this._selectionRenderModel.update(this._terminal, this._lastSelectionStart, this._lastSelectionEnd, this._lastSelectionColumnMode);\n      if (this._selectionRenderModel.hasSelection) {\n        oldViewportStart = this._selectionRenderModel.viewportCappedStartRow;\n        oldViewportEnd = this._selectionRenderModel.viewportCappedEndRow;\n      }\n    }\n\n    // Determine new selection viewport band and create overlays\n    let newViewportStart = 0;\n    let newViewportEnd = -1;\n    if (!start || !end) {\n      return;\n    }\n    this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);\n    if (this._selectionRenderModel.hasSelection) {\n      const viewportStartRow = this._selectionRenderModel.viewportStartRow;\n      const viewportEndRow = this._selectionRenderModel.viewportEndRow;\n      const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;\n      const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;\n\n      newViewportStart = viewportCappedStartRow;\n      newViewportEnd = viewportCappedEndRow;\n\n      // Create the selections\n      const documentFragment = this._document.createDocumentFragment();\n\n      if (columnSelectMode) {\n        const isXFlipped = start[0] > end[0];\n        documentFragment.appendChild(\n          this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)\n        );\n      } else {\n        // Draw first row\n        const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n        const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;\n        documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));\n        // Draw middle rows\n        const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;\n        documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));\n        // Draw final row\n        if (viewportCappedStartRow !== viewportCappedEndRow) {\n          // Only draw viewportEndRow if it's not the same as viewporttartRow\n          const finalEndCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;\n          documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, finalEndCol));\n        }\n      }\n      this._selectionContainer.appendChild(documentFragment);\n    }\n\n    // Compute minimal row range to redraw\n    let renderStartRow = Math.min(oldViewportStart, newViewportStart);\n    let renderEndRow = Math.max(oldViewportEnd, newViewportEnd);\n\n    if (renderEndRow >= 0) {\n      // Clamp to viewport\n      renderStartRow = Math.max(renderStartRow, 0);\n      renderEndRow = Math.min(renderEndRow, rows - 1);\n\n      // Ensure cursor row is included when a selection is present\n      const buffer = this._bufferService.buffer;\n      const cursorViewportRow = buffer.y;\n      if (this._selectionRenderModel.hasSelection && cursorViewportRow >= 0 && cursorViewportRow < rows) {\n        renderStartRow = Math.min(renderStartRow, cursorViewportRow);\n        renderEndRow = Math.max(renderEndRow, cursorViewportRow);\n      }\n\n      this.renderRows(renderStartRow, renderEndRow);\n    }\n\n    // Update last selection state\n    this._lastSelectionStart = start;\n    this._lastSelectionEnd = end;\n    this._lastSelectionColumnMode = columnSelectMode;\n  }\n\n  /**\n   * Creates a selection element at the specified position.\n   * @param row The row of the selection.\n   * @param colStart The start column.\n   * @param colEnd The end columns.\n   */\n  private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {\n    const element = this._document.createElement('div');\n    const left = colStart * this.dimensions.css.cell.width;\n    let width = this.dimensions.css.cell.width * (colEnd - colStart);\n    if (left + width > this.dimensions.css.canvas.width) {\n      width = this.dimensions.css.canvas.width - left;\n    }\n\n    element.style.height = `${rowCount * this.dimensions.css.cell.height}px`;\n    element.style.top = `${row * this.dimensions.css.cell.height}px`;\n    element.style.left = `${left}px`;\n    element.style.width = `${width}px`;\n    return element;\n  }\n\n  public handleCursorMove(): void {\n    // Reset idle timer on cursor movement (which happens on input)\n    this._cursorBlinkStateManager.restartBlinkAnimation();\n  }\n\n  private _handleOptionsChanged(): void {\n    // Force a refresh\n    this._updateDimensions();\n    // Refresh CSS\n    this._injectCss(this._themeService.colors);\n    // update spacing cache\n    this._widthCache.setFont(\n      this._optionsService.rawOptions.fontFamily,\n      this._optionsService.rawOptions.fontSize,\n      this._optionsService.rawOptions.fontWeight,\n      this._optionsService.rawOptions.fontWeightBold\n    );\n    this._setDefaultSpacing();\n  }\n\n  public clear(): void {\n    for (const e of this._rowElements) {\n      /**\n       * NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and\n       * `@testing-library/react`\n       *\n       * references:\n       * - https://github.com/testing-library/react-testing-library/issues/1146\n       * - https://github.com/jsdom/jsdom/issues/1245\n       */\n      e.replaceChildren();\n    }\n    if (this._rowHasBlinkingCellsCount > 0) {\n      this._rowHasBlinkingCells.fill(false);\n      this._rowHasBlinkingCellsCount = 0;\n      this._textBlinkStateManager.setNeedsBlinkInViewport(false);\n    }\n  }\n\n  public renderRows(start: number, end: number): void {\n    const buffer = this._bufferService.buffer;\n    const cursorAbsoluteY = buffer.ybase + buffer.y;\n    const cursorX = Math.min(buffer.x, this._bufferService.cols - 1);\n    const cursorBlink = this._coreService.decPrivateModes.cursorBlink ?? this._optionsService.rawOptions.cursorBlink;\n    const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? this._optionsService.rawOptions.cursorStyle;\n    const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;\n    const rowInfo = { hasBlinkingCells: false };\n\n    for (let y = start; y <= end; y++) {\n      const row = y + buffer.ydisp;\n      const rowElement = this._rowElements[y];\n      const lineData = buffer.lines.get(row);\n      if (!rowElement || !lineData) {\n        break;\n      }\n      rowElement.replaceChildren(\n        ...this._rowFactory.createRow(\n          lineData,\n          row,\n          row === cursorAbsoluteY,\n          cursorStyle,\n          cursorInactiveStyle,\n          cursorX,\n          cursorBlink,\n          this._textBlinkStateManager.isBlinkOn,\n          this.dimensions.css.cell.width,\n          this._widthCache,\n          -1,\n          -1,\n          rowInfo\n        )\n      );\n      this._setRowBlinkState(y, rowInfo.hasBlinkingCells);\n    }\n    this._updateTextBlinkState();\n  }\n\n  private get _terminalSelector(): string {\n    return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;\n  }\n\n  private _handleLinkHover(e: ILinkifierEvent): void {\n    this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);\n  }\n\n  private _handleLinkLeave(e: ILinkifierEvent): void {\n    this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false);\n  }\n\n  private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {\n    /**\n     * NOTE: The linkifier may send out of viewport y-values if:\n     * - negative y-value: the link started at a higher line\n     * - y-value >= maxY: the link ends at a line below viewport\n     *\n     * For negative y-values we can simply adjust x = 0,\n     * as higher up link start means, that everything from\n     * (0,0) is a link under top-down-left-right char progression\n     *\n     * Additionally there might be a small chance of out-of-sync x|y-values\n     * from a race condition of render updates vs. link event handler execution:\n     * - (sync) resize: chances terminal buffer in sync, schedules render update async\n     * - (async) link handler race condition: new buffer metrics, but still on old render state\n     * - (async) render update: brings term metrics and render state back in sync\n     */\n    // clip coords into viewport\n    if (y < 0) x = 0;\n    if (y2 < 0) x2 = 0;\n    const maxY = this._bufferService.rows - 1;\n    y = Math.max(Math.min(y, maxY), 0);\n    y2 = Math.max(Math.min(y2, maxY), 0);\n\n    cols = Math.min(cols, this._bufferService.cols);\n    const buffer = this._bufferService.buffer;\n    const cursorAbsoluteY = buffer.ybase + buffer.y;\n    const cursorX = Math.min(buffer.x, cols - 1);\n    const cursorBlink = this._optionsService.rawOptions.cursorBlink;\n    const cursorStyle = this._optionsService.rawOptions.cursorStyle;\n    const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;\n    const rowInfo = { hasBlinkingCells: false };\n\n    // refresh rows within link range\n    for (let i = y; i <= y2; ++i) {\n      const row = i + buffer.ydisp;\n      const rowElement = this._rowElements[i];\n      const bufferline = buffer.lines.get(row);\n      if (!rowElement || !bufferline) {\n        break;\n      }\n      rowElement.replaceChildren(\n        ...this._rowFactory.createRow(\n          bufferline,\n          row,\n          row === cursorAbsoluteY,\n          cursorStyle,\n          cursorInactiveStyle,\n          cursorX,\n          cursorBlink,\n          this._textBlinkStateManager.isBlinkOn,\n          this.dimensions.css.cell.width,\n          this._widthCache,\n          enabled ? (i === y ? x : 0) : -1,\n          enabled ? ((i === y2 ? x2 : cols) - 1) : -1,\n          rowInfo\n        )\n      );\n      this._setRowBlinkState(i, rowInfo.hasBlinkingCells);\n    }\n    this._updateTextBlinkState();\n  }\n\n  private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {\n    const previous = this._rowHasBlinkingCells[row];\n    if (previous === hasBlinkingCells) {\n      return;\n    }\n    this._rowHasBlinkingCells[row] = hasBlinkingCells;\n    this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;\n  }\n\n  private _updateTextBlinkState(): void {\n    this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);\n  }\n}\n\nclass CursorBlinkStateManager {\n  private _idleTimeout: number | undefined;\n  private _isIdlePaused: boolean = false;\n\n  constructor(\n    private readonly _rowContainer: HTMLElement,\n    private readonly _coreBrowserService: ICoreBrowserService\n  ) {\n    if (this._coreBrowserService.isFocused) {\n      this._resetIdleTimer();\n    }\n  }\n\n  public dispose(): void {\n    this._clearIdleTimer();\n  }\n\n  public restartBlinkAnimation(): void {\n    if (this._isIdlePaused) {\n      this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);\n    }\n    this._resetIdleTimer();\n  }\n\n  public pause(): void {\n    this._isIdlePaused = false;\n    this._clearIdleTimer();\n  }\n\n  public resume(): void {\n    this._isIdlePaused = false;\n    this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);\n    this._resetIdleTimer();\n  }\n\n  private _resetIdleTimer(): void {\n    this._isIdlePaused = false;\n    this._clearIdleTimer();\n    this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {\n      this._stopBlinkingDueToIdle();\n    }, RendererConstants.CURSOR_BLINK_IDLE_TIMEOUT);\n  }\n\n  private _clearIdleTimer(): void {\n    if (this._idleTimeout) {\n      this._coreBrowserService.window.clearTimeout(this._idleTimeout);\n      this._idleTimeout = undefined;\n    }\n  }\n\n  private _stopBlinkingDueToIdle(): void {\n    this._rowContainer.classList.add(CURSOR_BLINK_IDLE_CLASS);\n    this._isIdlePaused = true;\n    this._idleTimeout = undefined;\n  }\n}\n"
  },
  {
    "path": "src/browser/renderer/dom/DomRendererRowFactory.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport jsdom = require('jsdom');\nimport { assert } from 'chai';\nimport { DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';\nimport { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from 'common/buffer/Constants';\nimport { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { IBufferLine } from 'common/Types';\nimport { CellData } from 'common/buffer/CellData';\nimport { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from 'common/TestUtils.test';\nimport { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test';\nimport { TestWidthCache } from 'browser/renderer/dom/WidthCache.test';\n\nconst dom = new jsdom.JSDOM('');\n\n\ndescribe('DomRendererRowFactory', () => {\n  let dom: jsdom.JSDOM;\n  let rowFactory: DomRendererRowFactory;\n  let lineData: IBufferLine;\n  let widthCache: TestWidthCache;\n\n  beforeEach(() => {\n    dom = new jsdom.JSDOM('');\n    rowFactory = new DomRendererRowFactory(\n      dom.window.document,\n      new MockCharacterJoinerService(),\n      new MockOptionsService({ drawBoldTextInBrightColors: true }),\n      new MockCoreBrowserService(),\n      new MockCoreService(),\n      new MockDecorationService(),\n      new MockThemeService()\n    );\n    lineData = createEmptyLineData(2);\n    widthCache = new TestWidthCache();\n  });\n\n  describe('createRow', () => {\n    it('should not create anything for an empty row', () => {\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        ''\n      );\n    });\n\n    it('should set correct attributes for double width characters', () => {\n      widthCache.setWidths({ '語': 10 });\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, '語', 2));\n      // There should be no element for the following \"empty\" cell\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, '', 0));\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span>語</span>'\n      );\n    });\n\n    it('should add class for cursor and cursor style', () => {\n      for (const style of ['block', 'bar', 'underline']) {\n        const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          `<span class=\"xterm-cursor xterm-cursor-${style}\"> </span>`\n        );\n      }\n    });\n\n    it('should add class for cursor blink', () => {\n      const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        `<span class=\"xterm-cursor xterm-cursor-blink xterm-cursor-block\"> </span>`\n      );\n    });\n\n    it('should add class for inactive cursor', () => {\n      const coreBrowserService = new MockCoreBrowserService();\n      coreBrowserService.isFocused = false;\n      const rowFactory = new DomRendererRowFactory(\n        dom.window.document,\n        new MockCharacterJoinerService(),\n        new MockOptionsService({ drawBoldTextInBrightColors: true }),\n        coreBrowserService,\n        new MockCoreService(),\n        new MockDecorationService(),\n        new MockThemeService()\n      );\n      for (const inactiveStyle of ['outline', 'block', 'bar', 'underline', 'none']){\n        const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, true, 5, widthCache, -1, -1);\n        if (inactiveStyle === 'none') {\n          assert.equal(extractHtml(spans),\n            `<span class=\"xterm-cursor\"> </span>`);\n        } else {\n          assert.equal(extractHtml(spans),\n            `<span class=\"xterm-cursor xterm-cursor-${inactiveStyle}\"> </span>`);\n        }\n      }\n    });\n\n    it('should not display cursor for before initializing', () => {\n      const coreService = new MockCoreService();\n      coreService.isCursorInitialized = false;\n      const rowFactory = new DomRendererRowFactory(\n        dom.window.document,\n        new MockCharacterJoinerService(),\n        new MockOptionsService(),\n        new MockCoreBrowserService(),\n        coreService,\n        new MockDecorationService(),\n        new MockThemeService()\n      );\n      const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        `<span> </span>`\n      );\n    });\n\n    describe('attributes', () => {\n      it('should add class for bold', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-bold\">a</span>'\n        );\n      });\n\n      it('should add class for italic', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-italic\">a</span>'\n        );\n      });\n\n      it('should add class for dim', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-dim\">a</span>'\n        );\n      });\n\n      describe('underline', () => {\n        it('should add class for straight underline style', () => {\n          const cell = createCellData(0, 'a', 1);\n          cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;\n          cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;\n          cell.extended.underlineStyle = UnderlineStyle.SINGLE;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            '<span class=\"xterm-underline-1\">a</span>'\n          );\n        });\n        it('should add class for double underline style', () => {\n          const cell = createCellData(0, 'a', 1);\n          cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;\n          cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;\n          cell.extended.underlineStyle = UnderlineStyle.DOUBLE;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            '<span class=\"xterm-underline-2\">a</span>'\n          );\n        });\n        it('should add class for curly underline style', () => {\n          const cell = createCellData(0, 'a', 1);\n          cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;\n          cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;\n          cell.extended.underlineStyle = UnderlineStyle.CURLY;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            '<span class=\"xterm-underline-3\">a</span>'\n          );\n        });\n        it('should add class for double dotted style', () => {\n          const cell = createCellData(0, 'a', 1);\n          cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;\n          cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;\n          cell.extended.underlineStyle = UnderlineStyle.DOTTED;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            '<span class=\"xterm-underline-4\">a</span>'\n          );\n        });\n        it('should add class for dashed underline style', () => {\n          const cell = createCellData(0, 'a', 1);\n          cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;\n          cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;\n          cell.extended.underlineStyle = UnderlineStyle.DASHED;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            '<span class=\"xterm-underline-5\">a</span>'\n          );\n        });\n      });\n\n      it('should add class for overline', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-overline\">a</span>'\n        );\n      });\n\n      it('should add class for strikethrough', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-strikethrough\">a</span>'\n        );\n      });\n\n      it('should hide blinking text when blink is off', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BLINK | FgFlags.UNDERLINE;\n        cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;\n        cell.extended.underlineStyle = UnderlineStyle.SINGLE;\n        lineData.setCell(0, cell);\n        const onSpans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(onSpans),\n          '<span class=\"xterm-underline-1\">a</span>'\n        );\n        const offSpans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, false, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(offSpans),\n          '<span class=\"xterm-blink-hidden xterm-underline-1\">a</span>'\n        );\n      });\n\n      it('should add classes for 256 foreground colors', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= Attributes.CM_P256;\n        for (let i = 0; i < 256; i++) {\n          cell.fg &= ~Attributes.PCOLOR_MASK;\n          cell.fg |= i;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            `<span class=\"xterm-fg-${i}\">a</span>`\n          );\n        }\n      });\n\n      it('should add classes for 256 background colors', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.bg |= Attributes.CM_P256;\n        for (let i = 0; i < 256; i++) {\n          cell.bg &= ~Attributes.PCOLOR_MASK;\n          cell.bg |= i;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            `<span class=\"xterm-bg-${i}\">a</span>`\n          );\n        }\n      });\n\n      it('should correctly invert colors', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE;\n        cell.bg |= Attributes.CM_P16 | 1;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-bg-2 xterm-fg-1\">a</span>'\n        );\n      });\n\n      it('should correctly invert default fg color', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= FgFlags.INVERSE;\n        cell.bg |= Attributes.CM_P16 | 1;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-bg-257 xterm-fg-1\">a</span>'\n        );\n      });\n\n      it('should correctly invert default bg color', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span class=\"xterm-bg-1 xterm-fg-257\">a</span>'\n        );\n      });\n\n      it('should turn bold fg text bright', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= FgFlags.BOLD | Attributes.CM_P16;\n        for (let i = 0; i < 8; i++) {\n          cell.fg &= ~Attributes.PCOLOR_MASK;\n          cell.fg |= i;\n          lineData.setCell(0, cell);\n          const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n          assert.equal(extractHtml(spans),\n            `<span class=\"xterm-bold xterm-fg-${i + 8}\">a</span>`\n          );\n        }\n      });\n\n      it('should set style attribute for RBG', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3;\n        cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span style=\"background-color:#040506;color:#010203;\">a</span>'\n        );\n      });\n\n      it('should correctly invert RGB colors', () => {\n        const cell = createCellData(0, 'a', 1);\n        cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE;\n        cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;\n        lineData.setCell(0, cell);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span style=\"background-color:#010203;color:#040506;\">a</span>'\n        );\n      });\n    });\n\n    describe('selectionForeground', () => {\n      it('should force selected cells with content to be rendered above the background', () => {\n        lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n        lineData.setCell(1, createCellData(DEFAULT_ATTR, 'b', 1));\n        rowFactory.handleSelectionChanged([1, 0], [2, 0], false);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span>a</span><span style=\"background-color:#ff0000;\" class=\"xterm-decoration-top\">b</span>'\n        );\n      });\n      it('should force whitespace cells to be rendered above the background', () => {\n        lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1));\n        rowFactory.handleSelectionChanged([0, 0], [2, 0], false);\n        const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n        assert.equal(extractHtml(spans),\n          '<span style=\"background-color:#ff0000;\" class=\"xterm-decoration-top\"> a</span>'\n        );\n      });\n    });\n  });\n\n  describe('createRow with merged spans', () => {\n    // for test purpose assume all in codepoints 0..255 are merging\n    // const ALL_MERGING = new Uint8Array(FontMetrics.MAX);\n\n    beforeEach(() => {\n      lineData = createEmptyLineData(10);\n    });\n\n    it('should not create anything for an empty row', () => {\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        ''\n      );\n    });\n\n    it('can merge codepoints for equal spacing', () => {\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, 'b', 1));\n      lineData.setCell(2, createCellData(DEFAULT_ATTR, 'c', 1));\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span>abc</span>'\n      );\n    });\n\n    it('should not merge codepoints with different spacing', () => {\n      widthCache.setWidths({ '€': 2 });\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, '€', 1));\n      lineData.setCell(2, createCellData(DEFAULT_ATTR, 'c', 1));\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span>a</span><span style=\"letter-spacing: 3px;\">€</span><span>c</span>'\n      );\n    });\n\n    it('should not merge on FG change', () => {\n      const aColor1 = createCellData(DEFAULT_ATTR, 'a', 1);\n      aColor1.fg |= Attributes.CM_P16 | 1;\n      const bColor2 = createCellData(DEFAULT_ATTR, 'b', 1);\n      bColor2.fg |= Attributes.CM_P16 | 2;\n      lineData.setCell(0, aColor1);\n      lineData.setCell(1, aColor1);\n      lineData.setCell(2, bColor2);\n      lineData.setCell(3, bColor2);\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span class=\"xterm-fg-1\">aa</span><span class=\"xterm-fg-2\">bb</span>'\n      );\n    });\n\n    it('should not merge cursor cell', () => {\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(2, createCellData(DEFAULT_ATTR, 'X', 1));\n      lineData.setCell(3, createCellData(DEFAULT_ATTR, 'b', 1));\n      lineData.setCell(4, createCellData(DEFAULT_ATTR, 'b', 1));\n      const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span>aa</span><span class=\"xterm-cursor xterm-cursor-block\">X</span><span>bb</span>'\n      );\n    });\n\n    it('should handle BCE correctly', () => {\n      const nullCell = lineData.loadCell(0, new CellData());\n      nullCell.bg = Attributes.CM_P16 | 1;\n      lineData.setCell(2, nullCell);\n      nullCell.bg = Attributes.CM_P16 | 2;\n      lineData.setCell(3, nullCell);\n      lineData.setCell(4, nullCell);\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span>  </span><span class=\"xterm-bg-1\"> </span><span class=\"xterm-bg-2\">  </span>'\n      );\n    });\n\n    it('should handle BCE for multiple cells', () => {\n      const nullCell = lineData.loadCell(0, new CellData());\n      nullCell.bg = Attributes.CM_P16 | 1;\n      lineData.setCell(0, nullCell);\n      let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span class=\"xterm-bg-1\"> </span>'\n      );\n      lineData.setCell(1, nullCell);\n      spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span class=\"xterm-bg-1\">  </span>'\n      );\n      lineData.setCell(2, nullCell);\n      lineData.setCell(3, nullCell);\n      spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span class=\"xterm-bg-1\">    </span>'\n      );\n      lineData.setCell(4, createCellData(DEFAULT_ATTR, 'a', 1));\n      spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span class=\"xterm-bg-1\">    </span><span>a</span>'\n      );\n    });\n\n    it('should apply correct positive or negative spacing', () => {\n      widthCache.setWidths({ '€': 2, '語': 10, '𝄞': 7 });  // €: too small (+3px), 語: exact, 𝄞: too wide (-2px)\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, '€', 1));\n      lineData.setCell(2, createCellData(DEFAULT_ATTR, 'c', 1));\n      lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, '語', 2, 'c'.charCodeAt(0)]));\n      lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, '𝄞', 1, 'c'.charCodeAt(0)]));\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);\n      assert.equal(extractHtml(spans),\n        '<span>a</span><span style=\"letter-spacing: 3px;\">€</span><span>c語</span><span style=\"letter-spacing: -2px;\">𝄞</span>'\n      );\n    });\n\n    it('should not merge across link borders', () => {\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(2, createCellData(DEFAULT_ATTR, 'x', 1));\n      lineData.setCell(3, createCellData(DEFAULT_ATTR, 'x', 1));\n      lineData.setCell(4, createCellData(DEFAULT_ATTR, 'x', 1));\n      lineData.setCell(5, createCellData(DEFAULT_ATTR, 'b', 1));\n      lineData.setCell(6, createCellData(DEFAULT_ATTR, 'b', 1));\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, 2, 4);\n      assert.equal(extractHtml(spans),\n        '<span>aa</span><span style=\"text-decoration: underline;\">xxx</span><span>bb</span>'\n      );\n    });\n\n    it('empty cells included in link underline', () => {\n      lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1));\n      lineData.setCell(2, createCellData(DEFAULT_ATTR, 'x', 1));\n      lineData.setCell(4, createCellData(DEFAULT_ATTR, 'x', 1));\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, 2, 4);\n      assert.equal(extractHtml(spans),\n        '<span>aa</span><span style=\"text-decoration: underline;\">x x</span>'\n      );\n    });\n\n    it('link range gets capped to actual line borders', () => {\n      for (let i = 0; i < 10; ++i) {\n        lineData.setCell(i, createCellData(DEFAULT_ATTR, 'a', 1));\n      }\n      const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -100, 100);\n      assert.equal(extractHtml(spans),\n        '<span style=\"text-decoration: underline;\">aaaaaaaaaa</span>'\n      );\n    });\n\n  });\n\n  function extractHtml(spans: HTMLSpanElement[]): string {\n    const element = dom.window.document.createElement('div');\n    element.replaceChildren(...spans);\n    return element.innerHTML;\n  }\n\n  function createEmptyLineData(cols: number): IBufferLine {\n    const lineData = new BufferLine(cols);\n    for (let i = 0; i < cols; i++) {\n      lineData.setCell(i, NULL_CELL_DATA);\n    }\n    return lineData;\n  }\n});\n"
  },
  {
    "path": "src/browser/renderer/dom/DomRendererRowFactory.ts",
    "content": "/**\n * Copyright (c) 2018, 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferLine, ICellData, IColor } from 'common/Types';\nimport { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';\nimport { WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';\nimport { CellData } from 'common/buffer/CellData';\nimport { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';\nimport { channels, color } from 'common/Color';\nimport { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';\nimport { JoinedCellData } from 'browser/services/CharacterJoinerService';\nimport { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { WidthCache } from 'browser/renderer/dom/WidthCache';\nimport { IColorContrastCache } from 'browser/Types';\n\n\nexport const enum RowCss {\n  BOLD_CLASS = 'xterm-bold',\n  DIM_CLASS = 'xterm-dim',\n  ITALIC_CLASS = 'xterm-italic',\n  UNDERLINE_CLASS = 'xterm-underline',\n  OVERLINE_CLASS = 'xterm-overline',\n  STRIKETHROUGH_CLASS = 'xterm-strikethrough',\n  BLINK_HIDDEN_CLASS = 'xterm-blink-hidden',\n  CURSOR_CLASS = 'xterm-cursor',\n  CURSOR_BLINK_CLASS = 'xterm-cursor-blink',\n  CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block',\n  CURSOR_STYLE_OUTLINE_CLASS = 'xterm-cursor-outline',\n  CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar',\n  CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'\n}\n\n\nexport class DomRendererRowFactory {\n  private _workCell: CellData = new CellData();\n\n  private _selectionStart: [number, number] | undefined;\n  private _selectionEnd: [number, number] | undefined;\n  private _columnSelectMode: boolean = false;\n\n  public defaultSpacing = 0;\n\n  constructor(\n    private readonly _document: Document,\n    @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @IDecorationService private readonly _decorationService: IDecorationService,\n    @IThemeService private readonly _themeService: IThemeService\n  ) {}\n\n  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {\n    this._selectionStart = start;\n    this._selectionEnd = end;\n    this._columnSelectMode = columnSelectMode;\n  }\n\n  public createRow(\n    lineData: IBufferLine,\n    row: number,\n    isCursorRow: boolean,\n    cursorStyle: string | undefined,\n    cursorInactiveStyle: string | undefined,\n    cursorX: number,\n    cursorBlink: boolean,\n    blinkOn: boolean,\n    cellWidth: number,\n    widthCache: WidthCache,\n    linkStart: number,\n    linkEnd: number,\n    rowInfo?: { hasBlinkingCells: boolean }\n  ): HTMLSpanElement[] {\n\n    const elements: HTMLSpanElement[] = [];\n    if (rowInfo) {\n      rowInfo.hasBlinkingCells = false;\n    }\n    const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);\n    const colors = this._themeService.colors;\n\n    let lineLength = lineData.getNoBgTrimmedLength();\n    if (isCursorRow && lineLength < cursorX + 1) {\n      lineLength = cursorX + 1;\n    }\n\n    let charElement: HTMLSpanElement | undefined;\n    let cellAmount = 0;\n    let text = '';\n    let i = 0;\n    let oldBg = 0;\n    let oldFg = 0;\n    let oldExt = 0;\n    let oldLinkHover: number | boolean = false;\n    let oldSpacing = 0;\n    let oldIsInSelection: boolean = false;\n    let spacing = 0;\n    let skipJoinedCheckUntilX = 0;\n    const classes: string[] = [];\n\n    const hasHover = linkStart !== -1 && linkEnd !== -1;\n\n    for (let x = 0; x < lineLength; x++) {\n      lineData.loadCell(x, this._workCell);\n      let width = this._workCell.getWidth();\n\n      // The character to the left is a wide character, drawing is owned by the char at x-1\n      if (width === 0) {\n        continue;\n      }\n\n      // If true, indicates that the current character(s) to draw were joined.\n      let isJoined = false;\n\n      // Indicates whether this cell is part of a joined range that should be ignored as it cannot\n      // be rendered entirely, like the selection state differs across the range.\n      let isValidJoinRange = (x >= skipJoinedCheckUntilX);\n\n      let lastCharX = x;\n\n      // Process any joined character ranges as needed. Because of how the\n      // ranges are produced, we know that they are valid for the characters\n      // and attributes of our input.\n      let cell: ICellData = this._workCell;\n      if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {\n        const range = joinedRanges.shift()!;\n        // If the ligature's selection state is not consistent, don't join it. This helps the\n        // selection render correctly regardless whether they should be joined.\n        const firstSelectionState = this._isCellInSelection(range[0], row);\n        for (i = range[0] + 1; i < range[1]; i++) {\n          isValidJoinRange &&= (firstSelectionState === this._isCellInSelection(i, row));\n        }\n        // Similarly, if the cursor is in the ligature, don't join it.\n        isValidJoinRange &&= !isCursorRow || cursorX < range[0] || cursorX >= range[1];\n        if (!isValidJoinRange) {\n          skipJoinedCheckUntilX = range[1];\n        } else {\n          isJoined = true;\n\n          // We already know the exact start and end column of the joined range,\n          // so we get the string and width representing it directly\n          cell = new JoinedCellData(\n            this._workCell,\n            lineData.translateToString(true, range[0], range[1]),\n            range[1] - range[0]\n          );\n\n          // Skip over the cells occupied by this range in the loop\n          lastCharX = range[1] - 1;\n\n          // Recalculate width\n          width = cell.getWidth();\n        }\n      }\n\n      const isInSelection = this._isCellInSelection(x, row);\n      const isCursorCell = isCursorRow && x === cursorX;\n      const isLinkHover = hasHover && x >= linkStart && x <= linkEnd;\n      if (rowInfo && cell.isBlink()) {\n        rowInfo.hasBlinkingCells = true;\n      }\n      const isBlinkHidden = !blinkOn && cell.isBlink();\n      if (isBlinkHidden) {\n        classes.push(RowCss.BLINK_HIDDEN_CLASS);\n      }\n\n      let isDecorated = false;\n      this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {\n        isDecorated = true;\n      });\n\n      // get chars to render for this cell\n      let chars = cell.getChars() || WHITESPACE_CELL_CHAR;\n      if (chars === ' ' && (cell.isUnderline() || cell.isOverline())) {\n        chars = '\\xa0';\n      }\n\n      // lookup char render width and calc spacing\n      spacing = width * cellWidth - widthCache.get(chars, cell.isBold(), cell.isItalic());\n\n      if (!charElement) {\n        charElement = this._document.createElement('span');\n      } else {\n        /**\n         * chars can only be merged on existing span if:\n         * - existing span only contains mergeable chars (cellAmount != 0)\n         * - bg did not change (or both are in selection)\n         * - fg did not change (or both are in selection and selection fg is set)\n         * - ext did not change\n         * - underline from hover state did not change\n         * - cell content renders to same letter-spacing\n         * - cell is not cursor\n         */\n        if (\n          cellAmount\n          && (\n            (isInSelection && oldIsInSelection)\n            || (!isInSelection && !oldIsInSelection && cell.bg === oldBg)\n          )\n          && (\n            (isInSelection && oldIsInSelection && colors.selectionForeground)\n            || cell.fg === oldFg\n          )\n          && cell.extended.ext === oldExt\n          && isLinkHover === oldLinkHover\n          && spacing === oldSpacing\n          && !isCursorCell\n          && !isJoined\n          && !isDecorated\n          && isValidJoinRange\n        ) {\n          // no span alterations, thus only account chars skipping all code below\n          if (cell.isInvisible()) {\n            text += WHITESPACE_CELL_CHAR;\n          } else {\n            text += chars;\n          }\n          cellAmount++;\n          continue;\n        } else {\n          /**\n           * cannot merge:\n           * - apply left-over text to old span\n           * - create new span, reset state holders cellAmount & text\n           */\n          if (cellAmount) {\n            charElement.textContent = text;\n          }\n          charElement = this._document.createElement('span');\n          cellAmount = 0;\n          text = '';\n        }\n      }\n      // preserve conditions for next merger eval round\n      oldBg = cell.bg;\n      oldFg = cell.fg;\n      oldExt = cell.extended.ext;\n      oldLinkHover = isLinkHover;\n      oldSpacing = spacing;\n      oldIsInSelection = isInSelection;\n\n      if (isJoined) {\n        // The DOM renderer colors the background of the cursor but for ligatures all cells are\n        // joined. The workaround here is to show a cursor around the whole ligature so it shows up,\n        // the cursor looks the same when on any character of the ligature though\n        if (cursorX >= x && cursorX <= lastCharX) {\n          cursorX = x;\n        }\n      }\n\n      if (!this._coreService.isCursorHidden && isCursorCell && this._coreService.isCursorInitialized) {\n        classes.push(RowCss.CURSOR_CLASS);\n        if (this._coreBrowserService.isFocused) {\n          if (cursorBlink) {\n            classes.push(RowCss.CURSOR_BLINK_CLASS);\n          }\n          classes.push(\n            cursorStyle === 'bar'\n              ? RowCss.CURSOR_STYLE_BAR_CLASS\n              : cursorStyle === 'underline'\n                ? RowCss.CURSOR_STYLE_UNDERLINE_CLASS\n                : RowCss.CURSOR_STYLE_BLOCK_CLASS\n          );\n        } else {\n          if (cursorInactiveStyle) {\n            switch (cursorInactiveStyle) {\n              case 'outline':\n                classes.push(RowCss.CURSOR_STYLE_OUTLINE_CLASS);\n                break;\n              case 'block':\n                classes.push(RowCss.CURSOR_STYLE_BLOCK_CLASS);\n                break;\n              case 'bar':\n                classes.push(RowCss.CURSOR_STYLE_BAR_CLASS);\n                break;\n              case 'underline':\n                classes.push(RowCss.CURSOR_STYLE_UNDERLINE_CLASS);\n                break;\n              default:\n                break;\n            }\n          }\n        }\n      }\n\n      if (cell.isBold()) {\n        classes.push(RowCss.BOLD_CLASS);\n      }\n\n      if (cell.isItalic()) {\n        classes.push(RowCss.ITALIC_CLASS);\n      }\n\n      if (cell.isDim()) {\n        classes.push(RowCss.DIM_CLASS);\n      }\n\n      if (cell.isInvisible()) {\n        text = WHITESPACE_CELL_CHAR;\n      } else {\n        text = cell.getChars() || WHITESPACE_CELL_CHAR;\n      }\n\n      if (cell.isUnderline()) {\n        classes.push(`${RowCss.UNDERLINE_CLASS}-${cell.extended.underlineStyle}`);\n        if (text === ' ') {\n          text = '\\xa0'; // = &nbsp;\n        }\n        if (!cell.isUnderlineColorDefault()) {\n          if (cell.isUnderlineColorRGB()) {\n            charElement.style.textDecorationColor = `rgb(${AttributeData.toColorRGB(cell.getUnderlineColor()).join(',')})`;\n          } else {\n            let fg = cell.getUnderlineColor();\n            if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {\n              fg += 8;\n            }\n            charElement.style.textDecorationColor = colors.ansi[fg].css;\n          }\n        }\n      }\n\n      if (cell.isOverline()) {\n        classes.push(RowCss.OVERLINE_CLASS);\n        if (text === ' ') {\n          text = '\\xa0'; // = &nbsp;\n        }\n      }\n\n      if (cell.isStrikethrough()) {\n        classes.push(RowCss.STRIKETHROUGH_CLASS);\n      }\n\n      // apply link hover underline late, effectively overrides any previous text-decoration\n      // settings\n      if (isLinkHover) {\n        charElement.style.textDecoration = 'underline';\n      }\n\n      let fg = cell.getFgColor();\n      let fgColorMode = cell.getFgColorMode();\n      let bg = cell.getBgColor();\n      let bgColorMode = cell.getBgColorMode();\n      const isInverse = !!cell.isInverse();\n      if (isInverse) {\n        const temp = fg;\n        fg = bg;\n        bg = temp;\n        const temp2 = fgColorMode;\n        fgColorMode = bgColorMode;\n        bgColorMode = temp2;\n      }\n\n      // Apply any decoration foreground/background overrides, this must happen after inverse has\n      // been applied\n      let bgOverride: IColor | undefined;\n      let fgOverride: IColor | undefined;\n      let isTop = false;\n      this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {\n        if (d.options.layer !== 'top' && isTop) {\n          return;\n        }\n        if (d.backgroundColorRGB) {\n          bgColorMode = Attributes.CM_RGB;\n          bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;\n          bgOverride = d.backgroundColorRGB;\n        }\n        if (d.foregroundColorRGB) {\n          fgColorMode = Attributes.CM_RGB;\n          fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;\n          fgOverride = d.foregroundColorRGB;\n        }\n        isTop = d.options.layer === 'top';\n      });\n\n      // Apply selection\n      if (!isTop && isInSelection) {\n        // If in the selection, force the element to be above the selection to improve contrast and\n        // support opaque selections. The applies background is not actually needed here as\n        // selection is drawn in a seperate container, the main purpose of this to ensuring minimum\n        // contrast ratio\n        bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque;\n        bg = bgOverride.rgba >> 8 & 0xFFFFFF;\n        bgColorMode = Attributes.CM_RGB;\n        // Since an opaque selection is being rendered, the selection pretends to be a decoration to\n        // ensure text is drawn above the selection.\n        isTop = true;\n        // Apply selection foreground if applicable\n        if (colors.selectionForeground) {\n          fgColorMode = Attributes.CM_RGB;\n          fg = colors.selectionForeground.rgba >> 8 & 0xFFFFFF;\n          fgOverride = colors.selectionForeground;\n        }\n      }\n\n      // If it's a top decoration, render above the selection\n      if (isTop) {\n        classes.push('xterm-decoration-top');\n      }\n\n      // Background\n      let resolvedBg: IColor;\n      switch (bgColorMode) {\n        case Attributes.CM_P16:\n        case Attributes.CM_P256:\n          resolvedBg = colors.ansi[bg];\n          classes.push(`xterm-bg-${bg}`);\n          break;\n        case Attributes.CM_RGB:\n          resolvedBg = channels.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);\n          this._addStyle(charElement, `background-color:#${(bg >>> 0).toString(16).padStart(6, '0')}`);\n          break;\n        case Attributes.CM_DEFAULT:\n        default:\n          if (isInverse) {\n            resolvedBg = colors.foreground;\n            classes.push(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);\n          } else {\n            resolvedBg = colors.background;\n          }\n      }\n\n      // If there is no background override by now it's the original color, so apply dim if needed\n      if (!bgOverride) {\n        if (cell.isDim()) {\n          bgOverride = color.multiplyOpacity(resolvedBg, 0.5);\n        }\n      }\n\n      // Foreground\n      switch (fgColorMode) {\n        case Attributes.CM_P16:\n        case Attributes.CM_P256:\n          if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) {\n            fg += 8;\n          }\n          if (!this._applyMinimumContrast(charElement, resolvedBg, colors.ansi[fg], cell, bgOverride, undefined)) {\n            classes.push(`xterm-fg-${fg}`);\n          }\n          break;\n        case Attributes.CM_RGB:\n          const color = channels.toColor(\n            (fg >> 16) & 0xFF,\n            (fg >>  8) & 0xFF,\n            (fg      ) & 0xFF\n          );\n          if (!this._applyMinimumContrast(charElement, resolvedBg, color, cell, bgOverride, fgOverride)) {\n            this._addStyle(charElement, `color:#${fg.toString(16).padStart(6, '0')}`);\n          }\n          break;\n        case Attributes.CM_DEFAULT:\n        default:\n          if (!this._applyMinimumContrast(charElement, resolvedBg, colors.foreground, cell, bgOverride, fgOverride)) {\n            if (isInverse) {\n              classes.push(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);\n            }\n          }\n      }\n\n      // apply CSS classes\n      // slightly faster than using classList by omitting\n      // checks for doubled entries (code above should not have doublets)\n      if (classes.length) {\n        charElement.className = classes.join(' ');\n        classes.length = 0;\n      }\n\n      // exclude conditions for cell merging - never merge these\n      if (!isCursorCell && !isJoined && !isDecorated && isValidJoinRange) {\n        cellAmount++;\n      } else {\n        charElement.textContent = text;\n      }\n      // apply letter-spacing rule\n      if (spacing !== this.defaultSpacing) {\n        charElement.style.letterSpacing = `${spacing}px`;\n      }\n\n      elements.push(charElement);\n      x = lastCharX;\n    }\n\n    // postfix text of last merged span\n    if (charElement && cellAmount) {\n      charElement.textContent = text;\n    }\n\n    return elements;\n  }\n\n  private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean {\n    if (this._optionsService.rawOptions.minimumContrastRatio === 1 || treatGlyphAsBackgroundColor(cell.getCode())) {\n      return false;\n    }\n\n    // Try get from cache first, only use the cache when there are no decoration overrides\n    const cache = this._getContrastCache(cell);\n    let adjustedColor: IColor | undefined | null = undefined;\n    if (!bgOverride && !fgOverride) {\n      adjustedColor = cache.getColor(bg.rgba, fg.rgba);\n    }\n\n    // Calculate and store in cache\n    if (adjustedColor === undefined) {\n      // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from\n      // non-dim cells\n      const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1);\n      adjustedColor = color.ensureContrastRatio(bgOverride ?? bg, fgOverride ?? fg, ratio);\n      cache.setColor((bgOverride ?? bg).rgba, (fgOverride ?? fg).rgba, adjustedColor ?? null);\n    }\n\n    if (adjustedColor) {\n      this._addStyle(element, `color:${adjustedColor.css}`);\n      return true;\n    }\n\n    return false;\n  }\n\n  private _getContrastCache(cell: ICellData): IColorContrastCache {\n    if (cell.isDim()) {\n      return this._themeService.colors.halfContrastCache;\n    }\n    return this._themeService.colors.contrastCache;\n  }\n\n  private _addStyle(element: HTMLElement, style: string): void {\n    element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);\n  }\n\n  private _isCellInSelection(x: number, y: number): boolean {\n    const start = this._selectionStart;\n    const end = this._selectionEnd;\n    if (!start || !end) {\n      return false;\n    }\n    if (this._columnSelectMode) {\n      if (start[0] <= end[0]) {\n        return x >= start[0] && y >= start[1] &&\n          x < end[0] && y <= end[1];\n      }\n      return x < start[0] && y >= start[1] &&\n        x >= end[0] && y <= end[1];\n    }\n    return (y > start[1] && y < end[1]) ||\n        (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) ||\n        (start[1] < end[1] && y === end[1] && x < end[0]) ||\n        (start[1] < end[1] && y === start[1] && x >= start[0]);\n  }\n}\n"
  },
  {
    "path": "src/browser/renderer/dom/WidthCache.test.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as assert from 'assert';\nimport { IWidthCacheFontVariantCanvas, WidthCache, WidthCacheSettings } from 'browser/renderer/dom/WidthCache';\n\n\nclass MockWidthCacheFontVariantCanvas implements IWidthCacheFontVariantCanvas {\n  public widths: { [key: string]: number } = {};\n\n  public setFont(_fontFamily: string, _fontSize: number, _fontWeight: unknown, _italic: boolean): void {\n  }\n\n  public measure(c: string): number {\n    return this.widths[c] ?? 5;\n  }\n}\n\nexport class TestWidthCache extends WidthCache {\n  public get flat(): Float32Array {\n    return (this as any)._flat;\n  }\n  public get holey(): Map<string, number> | undefined {\n    return (this as any)._holey;\n  }\n  public get canvasElements(): MockWidthCacheFontVariantCanvas[] {\n    return (this as any)._canvasElements;\n  }\n\n  constructor() {\n    super(() => new MockWidthCacheFontVariantCanvas());\n  }\n\n  public setWidths(widths: { [key: string]: number }): void {\n    for (const canvas of this.canvasElements) {\n      canvas.widths = widths;\n    }\n  }\n}\n\n\nfunction castf32(v: number): number {\n  const buffer = new Float32Array(1);\n  buffer[0] = v;\n  return buffer[0];\n}\n\n\ndescribe('WidthCache', () => {\n  let wc: TestWidthCache;\n  beforeEach(() => {\n    wc = new TestWidthCache();\n    wc.setFont('monospace', 15, 'normal', 'bold');\n  });\n  describe('cache invalidation', () => {\n    beforeEach(() => {\n      wc.flat.fill(1.23);\n      wc.holey?.set('a', 2.34);\n    });\n    it('can cache values', () => {\n      assert.deepStrictEqual(wc.flat[0], castf32(1.23));\n      assert.deepStrictEqual(wc.holey?.get('a'), 2.34);\n      assert.deepStrictEqual(wc.holey?.size, 1);\n    });\n    it('clear resets cache entries', () => {\n      wc.clear();\n      assert.deepStrictEqual(wc.flat[0], castf32(WidthCacheSettings.FLAT_UNSET));\n      assert.deepStrictEqual(wc.holey?.get('a'), undefined);\n      assert.deepStrictEqual(wc.holey?.size, 0);\n    });\n    it('setFont with changed font name', () => {\n      wc.setFont('Arial', 15, 'normal', 'bold');\n      assert.deepStrictEqual(wc.flat[0], castf32(WidthCacheSettings.FLAT_UNSET));\n      assert.deepStrictEqual(wc.holey?.get('a'), undefined);\n      assert.deepStrictEqual(wc.holey?.size, 0);\n    });\n    it('setFont with changed font size', () => {\n      wc.setFont('monospace', 14, 'normal', 'bold');\n      assert.deepStrictEqual(wc.flat[0], castf32(WidthCacheSettings.FLAT_UNSET));\n      assert.deepStrictEqual(wc.holey?.get('a'), undefined);\n      assert.deepStrictEqual(wc.holey?.size, 0);\n    });\n    it('setFont with changed weight', () => {\n      wc.setFont('monospace', 15, '100', 'bold');\n      assert.deepStrictEqual(wc.flat[0], castf32(WidthCacheSettings.FLAT_UNSET));\n      assert.deepStrictEqual(wc.holey?.get('a'), undefined);\n      assert.deepStrictEqual(wc.holey?.size, 0);\n    });\n    it('setFont with changed weightBold', () => {\n      wc.setFont('monospace', 15, 'normal', '900');\n      assert.deepStrictEqual(wc.flat[0], castf32(WidthCacheSettings.FLAT_UNSET));\n      assert.deepStrictEqual(wc.holey?.get('a'), undefined);\n      assert.deepStrictEqual(wc.holey?.size, 0);\n    });\n    it('setFont with unchanged settings does not cache entries', () => {\n      wc.setFont('monospace', 15, 'normal', 'bold');\n      assert.deepStrictEqual(wc.flat[0], castf32(1.23));\n      assert.deepStrictEqual(wc.holey?.get('a'), 2.34);\n      assert.deepStrictEqual(wc.holey?.size, 1);\n    });\n  });\n  describe('get', () => {\n    it('store regular < WidthCacheSettings.FLAT_SIZE in flat', () => {\n      for (let i = 0; i < WidthCacheSettings.FLAT_SIZE + 10; ++i) {\n        const width = wc.get(String.fromCharCode(i), false, false);\n        assert.deepStrictEqual(width, 5);\n        if (i < WidthCacheSettings.FLAT_SIZE) {\n          assert.deepStrictEqual(wc.flat[i], 5);\n          assert.deepStrictEqual(wc.holey?.get(String.fromCharCode(i)), undefined);\n        } else {\n          assert.deepStrictEqual(wc.holey?.get(String.fromCharCode(i)), 5);\n        }\n      }\n    });\n    it('stores bold & italic in holey', () => {\n      // bold\n      let width = wc.get('b', true, false);\n      assert.deepStrictEqual(width, 5);\n      assert.deepStrictEqual(wc.holey?.get('bB'), 5);\n      // italic\n      width = wc.get('i', false, true);\n      assert.deepStrictEqual(width, 5);\n      assert.deepStrictEqual(wc.holey?.get('iI'), 5);\n      // bold&italic\n      width = wc.get('x', true, true);\n      assert.deepStrictEqual(width, 5);\n      assert.deepStrictEqual(wc.holey?.get('xBI'), 5);\n    });\n    it('can store any string', () => {\n      // regular\n      let width = wc.get('foo', false, false);\n      assert.deepStrictEqual(width, 5);\n      assert.deepStrictEqual(wc.holey?.get('foo'), 5);\n      // bold&italic\n      width = wc.get('bar&baz', true, true);\n      assert.deepStrictEqual(width, 5);\n      assert.deepStrictEqual(wc.holey?.get('bar&bazBI'), 5);\n    });\n  });\n});\n"
  },
  {
    "path": "src/browser/renderer/dom/WidthCache.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';\nimport { IDisposable } from 'common/Types';\nimport { FontWeight } from 'common/services/Services';\n\n\nexport const enum WidthCacheSettings {\n  /** sentinel for unset values in flat cache */\n  FLAT_UNSET = -9999,\n  /** size of flat cache, size-1 equals highest codepoint handled by flat */\n  FLAT_SIZE = 256,\n  /** char repeat for measuring */\n  REPEAT = 32\n}\n\n\nconst enum FontVariant {\n  REGULAR = 0,\n  BOLD = 1,\n  ITALIC = 2,\n  BOLD_ITALIC = 3\n}\n\nexport interface IWidthCacheFontVariantCanvas {\n  setFont(fontFamily: string, fontSize: number, fontWeight: FontWeight, italic: boolean): void;\n  measure(c: string): number;\n}\n\nexport class WidthCache implements IDisposable {\n  // flat cache for regular variant up to CacheSettings.FLAT_SIZE\n  // NOTE: ~4x faster access than holey (serving >>80% of terminal content)\n  //       It has a small memory footprint (only 1MB for full BMP caching),\n  //       still the sweet spot is not reached before touching 32k different codepoints,\n  //       thus we store the remaining <<20% of terminal data in a holey structure.\n  protected _flat = new Float32Array(WidthCacheSettings.FLAT_SIZE);\n\n  // holey cache for bold, italic and bold&italic for any string\n  // FIXME: can grow really big over time (~8.5 MB for full BMP caching),\n  //        so a shared API across terminals is needed\n  protected _holey: Map<string, number> | undefined;\n\n  private _font = '';\n  private _fontSize = 0;\n  private _weight: FontWeight = 'normal';\n  private _weightBold: FontWeight = 'bold';\n  private _canvasElements: IWidthCacheFontVariantCanvas[] = [];\n\n  constructor(\n    canvasFactory: () => IWidthCacheFontVariantCanvas = () => new WidthCacheFontVariantCanvas()\n  ) {\n    this._canvasElements = [\n      canvasFactory(),\n      canvasFactory(),\n      canvasFactory(),\n      canvasFactory()\n    ];\n\n    this.clear();\n  }\n\n  public dispose(): void {\n    this._canvasElements.length = 0;\n    this._holey = undefined; // free cache memory via GC\n  }\n\n  /**\n   * Clear the width cache.\n   */\n  public clear(): void {\n    this._flat.fill(WidthCacheSettings.FLAT_UNSET);\n    // .clear() has some overhead, re-assign instead (>3 times faster)\n    this._holey = new Map<string, number>();\n  }\n\n  /**\n   * Set the font for measuring.\n   * Must be called for any changes on font settings.\n   * Also clears the cache.\n   */\n  public setFont(font: string, fontSize: number, weight: FontWeight, weightBold: FontWeight): void {\n    // skip if nothing changed\n    if (\n      font === this._font &&\n      fontSize === this._fontSize &&\n      weight === this._weight &&\n      weightBold === this._weightBold\n    ) {\n      return;\n    }\n\n    this._font = font;\n    this._fontSize = fontSize;\n    this._weight = weight;\n    this._weightBold = weightBold;\n\n    this._canvasElements[FontVariant.REGULAR].setFont(font, fontSize, weight, false);\n    this._canvasElements[FontVariant.BOLD].setFont(font, fontSize, weightBold, false);\n    this._canvasElements[FontVariant.ITALIC].setFont(font, fontSize, weight, true);\n    this._canvasElements[FontVariant.BOLD_ITALIC].setFont(font, fontSize, weightBold, true);\n\n    this.clear();\n  }\n\n  /**\n   * Get the render width for cell content `c` with current font settings.\n   * `variant` denotes the font variant to be used.\n   */\n  public get(c: string, bold: boolean | number, italic: boolean | number): number {\n    let cp = 0;\n    if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < WidthCacheSettings.FLAT_SIZE) {\n      if (this._flat[cp] !== WidthCacheSettings.FLAT_UNSET) {\n        return this._flat[cp];\n      }\n      const width = this._measure(c, 0);\n      if (width > 0) {\n        this._flat[cp] = width;\n      }\n      return width;\n    }\n    let key = c;\n    if (bold) key += 'B';\n    if (italic) key += 'I';\n    let width = this._holey!.get(key);\n    if (width === undefined) {\n      let variant = 0;\n      if (bold) variant |= FontVariant.BOLD;\n      if (italic) variant |= FontVariant.ITALIC;\n      width = this._measure(c, variant);\n      if (width > 0) {\n        this._holey!.set(key, width);\n      }\n    }\n    return width;\n  }\n\n  protected _measure(c: string, variant: FontVariant): number {\n    return this._canvasElements[variant].measure(c);\n  }\n}\n\nclass WidthCacheFontVariantCanvas implements IWidthCacheFontVariantCanvas {\n  private _canvas: OffscreenCanvas | HTMLCanvasElement;\n  private _ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n\n  constructor() {\n    if (typeof OffscreenCanvas !== 'undefined') {\n      this._canvas = new OffscreenCanvas(1, 1);\n      this._ctx = throwIfFalsy(this._canvas.getContext('2d'));\n    } else {\n      this._canvas = document.createElement('canvas');\n      this._canvas.width = 1;\n      this._canvas.height = 1;\n      this._ctx = throwIfFalsy(this._canvas.getContext('2d'));\n    }\n  }\n\n  public setFont(fontFamily: string, fontSize: number, fontWeight: FontWeight, italic: boolean): void {\n    const fontStyle = italic ? 'italic' : '';\n    this._ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`.trim();\n  }\n\n  public measure(c: string): number {\n    return this._ctx.measureText(c).width;\n  }\n}\n"
  },
  {
    "path": "src/browser/renderer/shared/Constants.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport const INVERTED_DEFAULT_COLOR = 257;\n\nexport const enum RendererConstants {\n  /**\n   * The idle time after which cursor blinking stops.\n   */\n  CURSOR_BLINK_IDLE_TIMEOUT = 5 * 60 * 1000\n}\n"
  },
  {
    "path": "src/browser/renderer/shared/README.md",
    "content": "This folder contains files that are shared between the renderers.\n"
  },
  {
    "path": "src/browser/renderer/shared/RendererUtils.test.ts",
    "content": "/**\n * Copyright (c) 2023 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { computeNextVariantOffset } from 'browser/renderer/shared/RendererUtils';\nimport { assert } from 'chai';\n\ndescribe('RendererUtils', () => {\n  it('computeNextVariantOffset', () => {\n    const cellWidth = 11;\n    const doubleCellWidth = 22;\n    let line = 1;\n    let variantOffset = 0;\n\n    // should line 1\n    // =,_,=_,=_,\n    let cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];\n    let result = [1, 0, 0, 0];\n    for (let index = 0; index < cells.length; index++) {\n      const cell = cells[index];\n      variantOffset = computeNextVariantOffset(cell, line, variantOffset);\n      assert.equal(variantOffset, result[index]);\n    }\n\n    // should line 2\n    // ==__==__==_,_==__==__==,__==__==__==__==__==__,==__==__==__==__==__==,\n    line = 2;\n    variantOffset = 0;\n    cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];\n    result = [3, 2, 0, 2];\n    for (let index = 0; index < cells.length; index++) {\n      const cell = cells[index];\n      variantOffset = computeNextVariantOffset(cell, line, variantOffset);\n      assert.equal(variantOffset, result[index]);\n    }\n\n    // should line 3\n    // ===___===__,_===___===_,__===___===___===___==,=___===___===___===___,\n    line = 3;\n    variantOffset = 0;\n    cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];\n    result = [5, 4, 2, 0];\n    for (let index = 0; index < cells.length; index++) {\n      const cell = cells[index];\n      variantOffset = computeNextVariantOffset(cell, line, variantOffset);\n      assert.equal(variantOffset, result[index]);\n    }\n  });\n});\n"
  },
  {
    "path": "src/browser/renderer/shared/RendererUtils.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDimensions, IRenderDimensions } from 'browser/renderer/shared/Types';\n\nexport function throwIfFalsy<T>(value: T | undefined | null): T {\n  if (!value) {\n    throw new Error('value must not be falsy');\n  }\n  return value;\n}\n\nexport function isPowerlineGlyph(codepoint: number): boolean {\n  // Only return true for Powerline symbols which require\n  // different padding and should be excluded from minimum contrast\n  // ratio standards\n  return 0xE0A4 <= codepoint && codepoint <= 0xE0D6;\n}\n\nexport function isRestrictedPowerlineGlyph(codepoint: number): boolean {\n  return 0xE0B0 <= codepoint && codepoint <= 0xE0B7;\n}\n\nfunction isNerdFontGlyph(codepoint: number): boolean {\n  return 0xE000 <= codepoint && codepoint <= 0xF8FF;\n}\n\nfunction isBoxOrBlockGlyph(codepoint: number): boolean {\n  return 0x2500 <= codepoint && codepoint <= 0x259F;\n}\n\nexport function isEmoji(codepoint: number): boolean {\n  return (\n    codepoint >= 0x1F600 && codepoint <= 0x1F64F || // Emoticons\n    codepoint >= 0x1F300 && codepoint <= 0x1F5FF || // Misc Symbols and Pictographs\n    codepoint >= 0x1F680 && codepoint <= 0x1F6FF || // Transport and Map\n    codepoint >= 0x2600  && codepoint <= 0x26FF  || // Misc symbols\n    codepoint >= 0x2700  && codepoint <= 0x27BF  || // Dingbats\n    codepoint >= 0xFE00  && codepoint <= 0xFE0F  || // Variation Selectors\n    codepoint >= 0x1F900 && codepoint <= 0x1F9FF || // Supplemental Symbols and Pictographs\n    codepoint >= 0x1F1E6 && codepoint <= 0x1F1FF\n  );\n}\n\nexport function allowRescaling(codepoint: number | undefined, width: number, glyphSizeX: number, deviceCellWidth: number): boolean {\n  return (\n    // Is single cell width\n    width === 1 &&\n    // Glyph exceeds cell bounds, add 50% to avoid hurting readability by rescaling glyphs that\n    // barely overlap\n    glyphSizeX > Math.ceil(deviceCellWidth * 1.5) &&\n    // Never rescale ascii\n    codepoint !== undefined && codepoint > 0xFF &&\n    // Never rescale emoji\n    !isEmoji(codepoint) &&\n    // Never rescale powerline or nerd fonts\n    !isPowerlineGlyph(codepoint) && !isNerdFontGlyph(codepoint)\n  );\n}\n\nexport function treatGlyphAsBackgroundColor(codepoint: number): boolean {\n  return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint);\n}\n\nexport function createRenderDimensions(): IRenderDimensions {\n  return {\n    css: {\n      canvas: createDimension(),\n      cell: createDimension()\n    },\n    device: {\n      canvas: createDimension(),\n      cell: createDimension(),\n      char: {\n        width: 0,\n        height: 0,\n        left: 0,\n        top: 0\n      }\n    }\n  };\n}\n\nfunction createDimension(): IDimensions {\n  return {\n    width: 0,\n    height: 0\n  };\n}\n\nexport function computeNextVariantOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number {\n  return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2);\n}\n"
  },
  {
    "path": "src/browser/renderer/shared/SelectionRenderModel.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from 'browser/Types';\nimport { ISelectionRenderModel } from 'browser/renderer/shared/Types';\nimport { Terminal } from '@xterm/xterm';\n\nclass SelectionRenderModel implements ISelectionRenderModel {\n  public hasSelection!: boolean;\n  public columnSelectMode!: boolean;\n  public viewportStartRow!: number;\n  public viewportEndRow!: number;\n  public viewportCappedStartRow!: number;\n  public viewportCappedEndRow!: number;\n  public startCol!: number;\n  public endCol!: number;\n  public selectionStart: [number, number] | undefined;\n  public selectionEnd: [number, number] | undefined;\n\n  constructor() {\n    this.clear();\n  }\n\n  public clear(): void {\n    this.hasSelection = false;\n    this.columnSelectMode = false;\n    this.viewportStartRow = 0;\n    this.viewportEndRow = 0;\n    this.viewportCappedStartRow = 0;\n    this.viewportCappedEndRow = 0;\n    this.startCol = 0;\n    this.endCol = 0;\n    this.selectionStart = undefined;\n    this.selectionEnd = undefined;\n  }\n\n  public update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {\n    this.selectionStart = start;\n    this.selectionEnd = end;\n    // Selection does not exist\n    if (!start || !end || (start[0] === end[0] && start[1] === end[1])) {\n      this.clear();\n      return;\n    }\n\n    // Translate from buffer position to viewport position\n    const viewportY = terminal.buffers.active.ydisp;\n    const viewportStartRow = start[1] - viewportY;\n    const viewportEndRow = end[1] - viewportY;\n    const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n    const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);\n\n    // No need to draw the selection\n    if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) {\n      this.clear();\n      return;\n    }\n\n    this.hasSelection = true;\n    this.columnSelectMode = columnSelectMode;\n    this.viewportStartRow = viewportStartRow;\n    this.viewportEndRow = viewportEndRow;\n    this.viewportCappedStartRow = viewportCappedStartRow;\n    this.viewportCappedEndRow = viewportCappedEndRow;\n    this.startCol = start[0];\n    this.endCol = end[0];\n  }\n\n  public isCellSelected(terminal: Terminal, x: number, y: number): boolean {\n    if (!this.hasSelection) {\n      return false;\n    }\n    y -= terminal.buffer.active.viewportY;\n    if (this.columnSelectMode) {\n      if (this.startCol <= this.endCol) {\n        return x >= this.startCol && y >= this.viewportCappedStartRow &&\n          x < this.endCol && y <= this.viewportCappedEndRow;\n      }\n      return x < this.startCol && y >= this.viewportCappedStartRow &&\n        x >= this.endCol && y <= this.viewportCappedEndRow;\n    }\n    return (y > this.viewportStartRow && y < this.viewportEndRow) ||\n      (this.viewportStartRow === this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol && x < this.endCol) ||\n      (this.viewportStartRow < this.viewportEndRow && y === this.viewportEndRow && x < this.endCol) ||\n      (this.viewportStartRow < this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol);\n  }\n}\n\nexport function createSelectionRenderModel(): ISelectionRenderModel {\n  return new SelectionRenderModel();\n}\n"
  },
  {
    "path": "src/browser/renderer/shared/TextBlinkStateManager.test.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';\nimport { MockOptionsService } from 'common/TestUtils.test';\nimport type { ICoreBrowserService } from 'browser/services/Services';\nimport { Emitter } from 'common/Event';\n\nclass FakeWindow {\n  public nextId = 1;\n  public intervals = new Map<number, () => void>();\n\n  public setInterval(callback: () => void, _duration: number): number {\n    const id = this.nextId++;\n    this.intervals.set(id, callback);\n    return id;\n  }\n\n  public clearInterval(id: number): void {\n    this.intervals.delete(id);\n  }\n}\n\nfunction createManager(duration: number): {\n  manager: TextBlinkStateManager;\n  window: FakeWindow;\n  getRenderCount: () => number;\n} {\n  const fakeWindow = new FakeWindow();\n  let renderCount = 0;\n  const coreBrowserService: ICoreBrowserService = {\n    serviceBrand: undefined,\n    isFocused: true,\n    dpr: 1,\n    onDprChange: new Emitter<number>().event,\n    onWindowChange: new Emitter<Window & typeof globalThis>().event,\n    window: fakeWindow as any,\n    mainDocument: {} as any\n  };\n  const optionsService = new MockOptionsService({ blinkIntervalDuration: duration });\n  const manager = new TextBlinkStateManager(() => {\n    renderCount++;\n  }, coreBrowserService, optionsService);\n  return {\n    manager,\n    window: fakeWindow,\n    getRenderCount: () => renderCount\n  };\n}\n\nfunction getOnlyIntervalCallback(window: FakeWindow): () => void {\n  const iterator = window.intervals.values();\n  const first = iterator.next();\n  assert.ok(!first.done);\n  assert.ok(iterator.next().done);\n  return first.value;\n}\n\ndescribe('TextBlinkStateManager', () => {\n  it('starts interval only when needed', () => {\n    const { manager, window } = createManager(100);\n    assert.equal(window.intervals.size, 0);\n    manager.setNeedsBlinkInViewport(true);\n    assert.equal(window.intervals.size, 1);\n  });\n\n  it('stops interval and restores blink visibility when no longer needed', () => {\n    const { manager, window, getRenderCount } = createManager(100);\n    manager.setNeedsBlinkInViewport(true);\n    const tick = getOnlyIntervalCallback(window);\n    tick();\n    const rendersAfterTick = getRenderCount();\n    assert.equal(manager.isBlinkOn, false);\n    manager.setNeedsBlinkInViewport(false);\n    assert.equal(window.intervals.size, 0);\n    assert.equal(manager.isBlinkOn, true);\n    assert.equal(getRenderCount(), rendersAfterTick + 1);\n  });\n\n  it('pauses while viewport is hidden and resumes when visible', () => {\n    const { manager, window } = createManager(100);\n    manager.setNeedsBlinkInViewport(true);\n    assert.equal(window.intervals.size, 1);\n    manager.setViewportVisible(false);\n    assert.equal(window.intervals.size, 0);\n    manager.setViewportVisible(true);\n    assert.equal(window.intervals.size, 1);\n  });\n\n  it('does not start interval when duration is zero', () => {\n    const { manager, window } = createManager(0);\n    manager.setNeedsBlinkInViewport(true);\n    assert.equal(window.intervals.size, 0);\n  });\n});\n"
  },
  {
    "path": "src/browser/renderer/shared/TextBlinkStateManager.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICoreBrowserService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IOptionsService } from 'common/services/Services';\n\nexport class TextBlinkStateManager extends Disposable {\n  private _intervalDuration: number = 0;\n  private _interval: number | undefined;\n  private _blinkOn: boolean = true;\n  private _needsBlinkInViewport: boolean = false;\n  private _isViewportVisible: boolean = true;\n\n  constructor(\n    private readonly _renderCallback: () => void,\n    private readonly _coreBrowserService: ICoreBrowserService,\n    private readonly _optionsService: IOptionsService\n  ) {\n    super();\n    this._register(this._optionsService.onSpecificOptionChange('blinkIntervalDuration', duration => {\n      this.setIntervalDuration(duration);\n    }));\n    this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration);\n    this._register(toDisposable(() => this._clearInterval()));\n  }\n\n  public get isBlinkOn(): boolean {\n    return this._blinkOn;\n  }\n\n  public get isEnabled(): boolean {\n    return this._intervalDuration > 0;\n  }\n\n  public setNeedsBlinkInViewport(needsBlinkInViewport: boolean): void {\n    if (this._needsBlinkInViewport === needsBlinkInViewport) {\n      return;\n    }\n\n    this._needsBlinkInViewport = needsBlinkInViewport;\n    this._updateIntervalState();\n  }\n\n  public setViewportVisible(isVisible: boolean): void {\n    if (this._isViewportVisible === isVisible) {\n      return;\n    }\n\n    this._isViewportVisible = isVisible;\n    this._updateIntervalState();\n  }\n\n  public setIntervalDuration(duration: number): void {\n    if (duration === this._intervalDuration) {\n      return;\n    }\n\n    this._intervalDuration = duration;\n    this._clearInterval();\n    this._updateIntervalState();\n  }\n\n  private _updateIntervalState(): void {\n    const shouldBlink = this._intervalDuration > 0 && this._needsBlinkInViewport && this._isViewportVisible;\n    if (shouldBlink) {\n      if (this._interval !== undefined) {\n        return;\n      }\n      const wasBlinkOn = this._blinkOn;\n      this._blinkOn = true;\n      this._interval = this._coreBrowserService.window.setInterval(() => {\n        this._blinkOn = !this._blinkOn;\n        this._renderCallback();\n      }, this._intervalDuration);\n      if (!wasBlinkOn) {\n        this._renderCallback();\n      }\n      return;\n    }\n\n    this._clearInterval();\n    if (!this._blinkOn) {\n      this._blinkOn = true;\n      this._renderCallback();\n    }\n  }\n\n  private _clearInterval(): void {\n    if (this._interval !== undefined) {\n      this._coreBrowserService.window.clearInterval(this._interval);\n      this._interval = undefined;\n    }\n  }\n}\n"
  },
  {
    "path": "src/browser/renderer/shared/Types.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from '@xterm/xterm';\nimport { ITerminal } from 'browser/Types';\nimport { IDisposable } from 'common/Types';\nimport type { IEvent } from 'common/Event';\n\nexport interface IDimensions {\n  width: number;\n  height: number;\n}\n\nexport interface IOffset {\n  top: number;\n  left: number;\n}\n\nexport interface IRenderDimensions {\n  /**\n   * Dimensions measured in CSS pixels (ie. device pixels / device pixel ratio).\n   */\n  css: {\n    canvas: IDimensions;\n    cell: IDimensions;\n  };\n  /**\n   * Dimensions measured in actual pixels as rendered to the device.\n   */\n  device: {\n    canvas: IDimensions;\n    cell: IDimensions;\n    char: IDimensions & IOffset;\n  };\n}\n\nexport interface IRequestRedrawEvent {\n  start: number;\n  end: number;\n  /**\n   * Whether the redraw should happen synchronously. This is used to avoid\n   * flicker when the canvas is resized.\n   */\n  sync?: boolean;\n}\n\n/**\n * Note that IRenderer implementations should emit the refresh event after\n * rendering rows to the screen.\n */\nexport interface IRenderer extends IDisposable {\n  readonly dimensions: IRenderDimensions;\n\n  /**\n   * Fires when the renderer is requesting to be redrawn on the next animation\n   * frame but is _not_ a result of content changing (eg. selection changes).\n   */\n  readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;\n\n  dispose(): void;\n  handleDevicePixelRatioChange(): void;\n  handleResize(cols: number, rows: number): void;\n  handleCharSizeChanged(): void;\n  handleBlur(): void;\n  handleFocus(): void;\n  handleViewportVisibilityChange?(isVisible: boolean): void;\n  handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;\n  handleCursorMove(): void;\n  clear(): void;\n  renderRows(start: number, end: number): void;\n  clearTextureAtlas?(): void;\n}\n\nexport interface ISelectionRenderModel {\n  readonly hasSelection: boolean;\n  readonly columnSelectMode: boolean;\n  readonly viewportStartRow: number;\n  readonly viewportEndRow: number;\n  readonly viewportCappedStartRow: number;\n  readonly viewportCappedEndRow: number;\n  readonly startCol: number;\n  readonly endCol: number;\n  readonly selectionStart: [number, number] | undefined;\n  readonly selectionEnd: [number, number] | undefined;\n  clear(): void;\n  update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;\n  isCellSelected(terminal: Terminal, x: number, y: number): boolean;\n}\n"
  },
  {
    "path": "src/browser/scrollable/abstractScrollbar.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as dom from '../Dom';\nimport { FastDomNode } from './fastDomNode';\nimport { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor';\nimport { StandardWheelEvent } from './mouseEvent';\nimport { ScrollbarArrow, IScrollbarArrowOptions } from './scrollbarArrow';\nimport { ScrollbarState } from './scrollbarState';\nimport { ScrollbarVisibilityController } from './scrollbarVisibilityController';\nimport { Widget } from './widget';\nimport * as platform from 'common/Platform';\nimport { INewScrollPosition, Scrollable, ScrollbarVisibility } from './scrollable';\n\n/**\n * The orthogonal distance to the slider at which dragging \"resets\". This implements \"snapping\"\n */\nconst POINTER_DRAG_RESET_DISTANCE = 140;\n\nexport interface ISimplifiedPointerEvent {\n  buttons: number;\n  pageX: number;\n  pageY: number;\n}\n\nexport interface IScrollbarHost {\n  handleMouseWheel(mouseWheelEvent: StandardWheelEvent): void;\n  handleDragStart(): void;\n  handleDragEnd(): void;\n}\n\ninterface IAbstractScrollbarOptions {\n  lazyRender: boolean;\n  host: IScrollbarHost;\n  scrollbarState: ScrollbarState;\n  visibility: ScrollbarVisibility;\n  extraScrollbarClassName: string;\n  scrollable: Scrollable;\n  scrollByPage: boolean;\n}\n\nexport abstract class AbstractScrollbar extends Widget {\n\n  protected _host: IScrollbarHost;\n  protected _scrollable: Scrollable;\n  protected _scrollByPage: boolean;\n  private _lazyRender: boolean;\n  protected _scrollbarState: ScrollbarState;\n  protected _visibilityController: ScrollbarVisibilityController;\n  private _pointerMoveMonitor: GlobalPointerMoveMonitor;\n\n  public domNode: FastDomNode<HTMLElement>;\n  public slider!: FastDomNode<HTMLElement>;\n\n  protected _shouldRender: boolean;\n\n  constructor(opts: IAbstractScrollbarOptions) {\n    super();\n    this._lazyRender = opts.lazyRender;\n    this._host = opts.host;\n    this._scrollable = opts.scrollable;\n    this._scrollByPage = opts.scrollByPage;\n    this._scrollbarState = opts.scrollbarState;\n    this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'xterm-visible xterm-scrollbar ' + opts.extraScrollbarClassName, 'xterm-invisible xterm-scrollbar ' + opts.extraScrollbarClassName));\n    this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());\n    this._pointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());\n    this._shouldRender = true;\n    this.domNode = new FastDomNode(document.createElement('div'));\n    this.domNode.setAttribute('role', 'presentation');\n    this.domNode.setAttribute('aria-hidden', 'true');\n\n    this._visibilityController.setDomNode(this.domNode);\n    this.domNode.setPosition('absolute');\n\n    this._register(dom.addDisposableListener(this.domNode.domNode, dom.eventType.POINTER_DOWN, (e: PointerEvent) => this._domNodePointerDown(e)));\n  }\n\n  // ----------------- creation\n\n  /**\n   * Creates the dom node for an arrow & adds it to the container\n   */\n  protected _createArrow(opts: IScrollbarArrowOptions): ScrollbarArrow {\n    const arrow = this._register(new ScrollbarArrow(opts));\n    this.domNode.domNode.appendChild(arrow.bgDomNode);\n    this.domNode.domNode.appendChild(arrow.domNode);\n    return arrow;\n  }\n\n  /**\n   * Creates the slider dom node, adds it to the container & hooks up the events\n   */\n  protected _createSlider(top: number, left: number, width: number | undefined, height: number | undefined): void {\n    this.slider = new FastDomNode(document.createElement('div'));\n    this.slider.setClassName('xterm-slider');\n    this.slider.setPosition('absolute');\n    this.slider.setTop(top);\n    this.slider.setLeft(left);\n    if (typeof width === 'number') {\n      this.slider.setWidth(width);\n    }\n    if (typeof height === 'number') {\n      this.slider.setHeight(height);\n    }\n    this.slider.setLayerHinting(true);\n    this.slider.setContain('strict');\n\n    this.domNode.domNode.appendChild(this.slider.domNode);\n\n    this._register(dom.addDisposableListener(\n      this.slider.domNode,\n      dom.eventType.POINTER_DOWN,\n      (e: PointerEvent) => {\n        if (e.button === 0) {\n          e.preventDefault();\n          this._sliderPointerDown(e);\n        }\n      }\n    ));\n\n    this._onclick(this.slider.domNode, e => {\n      if (e.leftButton) {\n        e.stopPropagation();\n      }\n    });\n  }\n\n  // ----------------- Update state\n\n  protected _handleElementSize(visibleSize: number): boolean {\n    if (this._scrollbarState.setVisibleSize(visibleSize)) {\n      this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());\n      this._shouldRender = true;\n      if (!this._lazyRender) {\n        this.render();\n      }\n    }\n    return this._shouldRender;\n  }\n\n  protected _handleElementScrollSize(elementScrollSize: number): boolean {\n    if (this._scrollbarState.setScrollSize(elementScrollSize)) {\n      this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());\n      this._shouldRender = true;\n      if (!this._lazyRender) {\n        this.render();\n      }\n    }\n    return this._shouldRender;\n  }\n\n  protected _handleElementScrollPosition(elementScrollPosition: number): boolean {\n    if (this._scrollbarState.setScrollPosition(elementScrollPosition)) {\n      this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());\n      this._shouldRender = true;\n      if (!this._lazyRender) {\n        this.render();\n      }\n    }\n    return this._shouldRender;\n  }\n\n  // ----------------- rendering\n\n  public beginReveal(): void {\n    this._visibilityController.setShouldBeVisible(true);\n  }\n\n  public beginHide(): void {\n    this._visibilityController.setShouldBeVisible(false);\n  }\n\n  public render(): void {\n    if (!this._shouldRender) {\n      return;\n    }\n    this._shouldRender = false;\n\n    this._renderDomNode(this._scrollbarState.getRectangleLargeSize(), this._scrollbarState.getRectangleSmallSize());\n    this._updateSlider(this._scrollbarState.getSliderSize(), this._scrollbarState.getArrowSize() + this._scrollbarState.getSliderPosition());\n  }\n  // ----------------- DOM events\n\n  private _domNodePointerDown(e: PointerEvent): void {\n    if (e.target !== this.domNode.domNode) {\n      return;\n    }\n    this._handlePointerDown(e);\n  }\n\n  public delegatePointerDown(e: PointerEvent): void {\n    const domTop = this.domNode.domNode.getClientRects()[0].top;\n    const sliderStart = domTop + this._scrollbarState.getSliderPosition();\n    const sliderStop = domTop + this._scrollbarState.getSliderPosition() + this._scrollbarState.getSliderSize();\n    const pointerPos = this._sliderPointerPosition(e);\n    if (sliderStart <= pointerPos && pointerPos <= sliderStop) {\n      if (e.button === 0) {\n        e.preventDefault();\n        this._sliderPointerDown(e);\n      }\n    } else {\n      this._handlePointerDown(e);\n    }\n  }\n\n  private _handlePointerDown(e: PointerEvent): void {\n    let offsetX: number;\n    let offsetY: number;\n    if (e.target === this.domNode.domNode && typeof e.offsetX === 'number' && typeof e.offsetY === 'number') {\n      offsetX = e.offsetX;\n      offsetY = e.offsetY;\n    } else {\n      const domNodePosition = dom.getDomNodePagePosition(this.domNode.domNode);\n      offsetX = e.pageX - domNodePosition.left;\n      offsetY = e.pageY - domNodePosition.top;\n    }\n\n    const offset = this._pointerDownRelativePosition(offsetX, offsetY);\n    this._setDesiredScrollPositionNow(\n      this._scrollByPage\n        ? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset)\n        : this._scrollbarState.getDesiredScrollPositionFromOffset(offset)\n    );\n\n    if (e.button === 0) {\n      e.preventDefault();\n      this._sliderPointerDown(e);\n    }\n  }\n\n  private _sliderPointerDown(e: PointerEvent): void {\n    if (!e.target || !(e.target instanceof Element)) {\n      return;\n    }\n    const initialPointerPosition = this._sliderPointerPosition(e);\n    const initialPointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(e);\n    const initialScrollbarState = this._scrollbarState.clone();\n    this.slider.toggleClassName('xterm-active', true);\n\n    this._pointerMoveMonitor.startMonitoring(\n      e.target,\n      e.pointerId,\n      e.buttons,\n      (pointerMoveData: PointerEvent) => {\n        const pointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(pointerMoveData);\n        const pointerOrthogonalDelta = Math.abs(pointerOrthogonalPosition - initialPointerOrthogonalPosition);\n\n        if (platform.isWindows && pointerOrthogonalDelta > POINTER_DRAG_RESET_DISTANCE) {\n          this._setDesiredScrollPositionNow(initialScrollbarState.getScrollPosition());\n          return;\n        }\n\n        const pointerPosition = this._sliderPointerPosition(pointerMoveData);\n        const pointerDelta = pointerPosition - initialPointerPosition;\n        this._setDesiredScrollPositionNow(initialScrollbarState.getDesiredScrollPositionFromDelta(pointerDelta));\n      },\n      () => {\n        this.slider.toggleClassName('xterm-active', false);\n        this._host.handleDragEnd();\n      }\n    );\n\n    this._host.handleDragStart();\n  }\n\n  private _setDesiredScrollPositionNow(_desiredScrollPosition: number): void {\n\n    const desiredScrollPosition: INewScrollPosition = {};\n    this.writeScrollPosition(desiredScrollPosition, _desiredScrollPosition);\n\n    this._scrollable.setScrollPositionNow(desiredScrollPosition);\n  }\n\n  public updateScrollbarSize(scrollbarSize: number): void {\n    this._updateScrollbarSize(scrollbarSize);\n    this._scrollbarState.setScrollbarSize(scrollbarSize);\n    this._shouldRender = true;\n    if (!this._lazyRender) {\n      this.render();\n    }\n  }\n\n  public isNeeded(): boolean {\n    return this._scrollbarState.isNeeded();\n  }\n\n  // ----------------- Overwrite these\n\n  protected abstract _renderDomNode(largeSize: number, smallSize: number): void;\n  protected abstract _updateSlider(sliderSize: number, sliderPosition: number): void;\n\n  protected abstract _pointerDownRelativePosition(offsetX: number, offsetY: number): number;\n  protected abstract _sliderPointerPosition(e: ISimplifiedPointerEvent): number;\n  protected abstract _sliderOrthogonalPointerPosition(e: ISimplifiedPointerEvent): number;\n  protected abstract _updateScrollbarSize(size: number): void;\n\n  public abstract writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void;\n}\n"
  },
  {
    "path": "src/browser/scrollable/fastDomNode.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nexport class FastDomNode<T extends HTMLElement> {\n\n  private _width: string = '';\n  private _height: string = '';\n  private _top: string = '';\n  private _left: string = '';\n  private _bottom: string = '';\n  private _right: string = '';\n  private _className: string = '';\n  private _position: string = '';\n  private _layerHint: boolean = false;\n  private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint' = 'none';\n\n  constructor(\n    public readonly domNode: T\n  ) { }\n\n  public setWidth(_width: number | string): void {\n    const width = numberAsPixels(_width);\n    if (this._width === width) {\n      return;\n    }\n    this._width = width;\n    this.domNode.style.width = this._width;\n  }\n\n  public setHeight(_height: number | string): void {\n    const height = numberAsPixels(_height);\n    if (this._height === height) {\n      return;\n    }\n    this._height = height;\n    this.domNode.style.height = this._height;\n  }\n\n  public setTop(_top: number | string): void {\n    const top = numberAsPixels(_top);\n    if (this._top === top) {\n      return;\n    }\n    this._top = top;\n    this.domNode.style.top = this._top;\n  }\n\n  public setLeft(_left: number | string): void {\n    const left = numberAsPixels(_left);\n    if (this._left === left) {\n      return;\n    }\n    this._left = left;\n    this.domNode.style.left = this._left;\n  }\n\n  public setBottom(_bottom: number | string): void {\n    const bottom = numberAsPixels(_bottom);\n    if (this._bottom === bottom) {\n      return;\n    }\n    this._bottom = bottom;\n    this.domNode.style.bottom = this._bottom;\n  }\n\n  public setRight(_right: number | string): void {\n    const right = numberAsPixels(_right);\n    if (this._right === right) {\n      return;\n    }\n    this._right = right;\n    this.domNode.style.right = this._right;\n  }\n\n  public setClassName(className: string): void {\n    if (this._className === className) {\n      return;\n    }\n    this._className = className;\n    this.domNode.className = this._className;\n  }\n\n  public toggleClassName(className: string, shouldHaveIt?: boolean): void {\n    this.domNode.classList.toggle(className, shouldHaveIt);\n    this._className = this.domNode.className;\n  }\n\n  public setPosition(position: string): void {\n    if (this._position === position) {\n      return;\n    }\n    this._position = position;\n    this.domNode.style.position = this._position;\n  }\n\n  public setLayerHinting(layerHint: boolean): void {\n    if (this._layerHint === layerHint) {\n      return;\n    }\n    this._layerHint = layerHint;\n    if (layerHint) {\n      this.domNode.style.transform = 'translate3d(0px, 0px, 0px)';\n    } else {\n      this.domNode.style.transform = '';\n    }\n  }\n\n  public setContain(contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'): void {\n    if (this._contain === contain) {\n      return;\n    }\n    this._contain = contain;\n    this.domNode.style.contain = this._contain;\n  }\n\n  public setAttribute(name: string, value: string): void {\n    this.domNode.setAttribute(name, value);\n  }\n\n}\n\nfunction numberAsPixels(value: number | string): string {\n  return (typeof value === 'number' ? `${value}px` : value);\n}\n"
  },
  {
    "path": "src/browser/scrollable/globalPointerMoveMonitor.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as dom from '../Dom';\nimport { DisposableStore, IDisposable, toDisposable } from 'common/Lifecycle';\n\ntype PointerMoveCallback = (event: PointerEvent) => void;\ntype OnStopCallback = () => void;\n\nexport class GlobalPointerMoveMonitor implements IDisposable {\n\n  private readonly _hooks = new DisposableStore();\n  private _pointerMoveCallback: PointerMoveCallback | null = null;\n  private _onStopCallback: OnStopCallback | null = null;\n\n  public dispose(): void {\n    this.stopMonitoring(false);\n    this._hooks.dispose();\n  }\n\n  public stopMonitoring(invokeStopCallback: boolean): void {\n    if (!this.isMonitoring()) {\n      return;\n    }\n\n    this._hooks.clear();\n    this._pointerMoveCallback = null;\n    const onStopCallback = this._onStopCallback;\n    this._onStopCallback = null;\n\n    if (invokeStopCallback && onStopCallback) {\n      onStopCallback();\n    }\n  }\n\n  public isMonitoring(): boolean {\n    return !!this._pointerMoveCallback;\n  }\n\n  public startMonitoring(\n    initialElement: Element,\n    pointerId: number,\n    initialButtons: number,\n    pointerMoveCallback: PointerMoveCallback,\n    onStopCallback: OnStopCallback\n  ): void {\n    if (this.isMonitoring()) {\n      this.stopMonitoring(false);\n    }\n    this._pointerMoveCallback = pointerMoveCallback;\n    this._onStopCallback = onStopCallback;\n\n    let eventSource: Element | Window = initialElement;\n\n    try {\n      initialElement.setPointerCapture(pointerId);\n      this._hooks.add(toDisposable(() => {\n        try {\n          initialElement.releasePointerCapture(pointerId);\n        } catch {\n          // ignore\n        }\n      }));\n    } catch {\n      eventSource = dom.getWindow(initialElement);\n    }\n\n    this._hooks.add(dom.addDisposableListener(\n      eventSource,\n      dom.eventType.POINTER_MOVE,\n      (e) => {\n        if (e.buttons !== initialButtons) {\n          this.stopMonitoring(true);\n          return;\n        }\n\n        e.preventDefault();\n        this._pointerMoveCallback!(e);\n      }\n    ));\n\n    this._hooks.add(dom.addDisposableListener(\n      eventSource,\n      dom.eventType.POINTER_UP,\n      (e: PointerEvent) => this.stopMonitoring(true)\n    ));\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/horizontalScrollbar.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { AbstractScrollbar, ISimplifiedPointerEvent, IScrollbarHost } from './abstractScrollbar';\nimport { IScrollableElementResolvedOptions } from './scrollableElementOptions';\nimport { ScrollbarState } from './scrollbarState';\nimport { INewScrollPosition, Scrollable, ScrollbarVisibility, IScrollEvent } from './scrollable';\n\nexport class HorizontalScrollbar extends AbstractScrollbar {\n\n  constructor(scrollable: Scrollable, options: IScrollableElementResolvedOptions, host: IScrollbarHost) {\n    const scrollDimensions = scrollable.getScrollDimensions();\n    const scrollPosition = scrollable.getCurrentScrollPosition();\n    super({\n      lazyRender: options.lazyRender,\n      host: host,\n      scrollbarState: new ScrollbarState(\n        (options.horizontalHasArrows ? options.horizontalScrollbarSize : 0),\n        (options.horizontal === ScrollbarVisibility.HIDDEN ? 0 : options.horizontalScrollbarSize),\n        (options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize),\n        scrollDimensions.width,\n        scrollDimensions.scrollWidth,\n        scrollPosition.scrollLeft\n      ),\n      visibility: options.horizontal,\n      extraScrollbarClassName: 'xterm-horizontal',\n      scrollable: scrollable,\n      scrollByPage: options.scrollByPage\n    });\n\n    if (options.horizontalHasArrows) {\n      throw new Error('horizontalHasArrows is not supported in xterm.js');\n    }\n\n    this._createSlider(Math.floor((options.horizontalScrollbarSize - options.horizontalSliderSize) / 2), 0, undefined, options.horizontalSliderSize);\n  }\n\n  protected _updateSlider(sliderSize: number, sliderPosition: number): void {\n    this.slider.setWidth(sliderSize);\n    this.slider.setLeft(sliderPosition);\n  }\n\n  protected _renderDomNode(largeSize: number, smallSize: number): void {\n    this.domNode.setWidth(largeSize);\n    this.domNode.setHeight(smallSize);\n    this.domNode.setLeft(0);\n    this.domNode.setBottom(0);\n  }\n\n  public handleScroll(e: IScrollEvent): boolean {\n    this._shouldRender = this._handleElementScrollSize(e.scrollWidth) || this._shouldRender;\n    this._shouldRender = this._handleElementScrollPosition(e.scrollLeft) || this._shouldRender;\n    this._shouldRender = this._handleElementSize(e.width) || this._shouldRender;\n    return this._shouldRender;\n  }\n\n  protected _pointerDownRelativePosition(offsetX: number, offsetY: number): number {\n    return offsetX;\n  }\n\n  protected _sliderPointerPosition(e: ISimplifiedPointerEvent): number {\n    return e.pageX;\n  }\n\n  protected _sliderOrthogonalPointerPosition(e: ISimplifiedPointerEvent): number {\n    return e.pageY;\n  }\n\n  protected _updateScrollbarSize(size: number): void {\n    this.slider.setHeight(size);\n  }\n\n  public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void {\n    target.scrollLeft = scrollPosition;\n  }\n\n  public updateOptions(options: IScrollableElementResolvedOptions): void {\n    this.updateScrollbarSize(options.horizontal === ScrollbarVisibility.HIDDEN ? 0 : options.horizontalScrollbarSize);\n    this._scrollbarState.setOppositeScrollbarSize(options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize);\n    this._visibilityController.setVisibility(options.horizontal);\n    this._scrollByPage = options.scrollByPage;\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/mouseEvent.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as platform from 'common/Platform';\n\ninterface IWindowChainElement {\n  readonly window: WeakRef<Window>;\n  readonly iframeElement: Element | null;\n}\n\nconst sameOriginWindowChainCache = new WeakMap<Window, IWindowChainElement[] | null>();\n\nfunction getParentWindowIfSameOrigin(w: Window): Window | null {\n  if (!w.parent || w.parent === w) {\n    return null;\n  }\n\n  try {\n    const location = w.location;\n    const parentLocation = w.parent.location;\n    if (location.origin !== 'null' && parentLocation.origin !== 'null' && location.origin !== parentLocation.origin) {\n      return null;\n    }\n  } catch {\n    return null;\n  }\n\n  return w.parent;\n}\n\nclass IframeUtils {\n\n  private static _getSameOriginWindowChain(targetWindow: Window): IWindowChainElement[] {\n    let windowChainCache = sameOriginWindowChainCache.get(targetWindow);\n    if (!windowChainCache) {\n      windowChainCache = [];\n      sameOriginWindowChainCache.set(targetWindow, windowChainCache);\n      let w: Window | null = targetWindow;\n      let parent: Window | null;\n      do {\n        parent = getParentWindowIfSameOrigin(w);\n        if (parent) {\n          windowChainCache.push({\n            window: new WeakRef(w),\n            iframeElement: w.frameElement ?? null\n          });\n        } else {\n          windowChainCache.push({\n            window: new WeakRef(w),\n            iframeElement: null\n          });\n        }\n        w = parent;\n      } while (w);\n    }\n    return windowChainCache.slice(0);\n  }\n\n  public static getPositionOfChildWindowRelativeToAncestorWindow(childWindow: Window, ancestorWindow: Window | null): { top: number, left: number } {\n\n    if (!ancestorWindow || childWindow === ancestorWindow) {\n      return {\n        top: 0,\n        left: 0\n      };\n    }\n\n    let top = 0;\n    let left = 0;\n\n    const windowChain = this._getSameOriginWindowChain(childWindow);\n\n    for (const windowChainEl of windowChain) {\n      const windowInChain = windowChainEl.window.deref();\n      top += windowInChain?.scrollY ?? 0;\n      left += windowInChain?.scrollX ?? 0;\n\n      if (windowInChain === ancestorWindow) {\n        break;\n      }\n\n      if (!windowChainEl.iframeElement) {\n        break;\n      }\n\n      const boundingRect = windowChainEl.iframeElement.getBoundingClientRect();\n      top += boundingRect.top;\n      left += boundingRect.left;\n    }\n\n    return {\n      top: top,\n      left: left\n    };\n  }\n}\n\nexport interface IMouseEvent {\n  readonly browserEvent: MouseEvent;\n  readonly leftButton: boolean;\n  readonly middleButton: boolean;\n  readonly rightButton: boolean;\n  readonly buttons: number;\n  readonly target: HTMLElement;\n  readonly detail: number;\n  readonly posx: number;\n  readonly posy: number;\n  readonly ctrlKey: boolean;\n  readonly shiftKey: boolean;\n  readonly altKey: boolean;\n  readonly metaKey: boolean;\n  readonly timestamp: number;\n\n  preventDefault(): void;\n  stopPropagation(): void;\n}\n\nexport class StandardMouseEvent implements IMouseEvent {\n\n  public readonly browserEvent: MouseEvent;\n\n  public readonly leftButton: boolean;\n  public readonly middleButton: boolean;\n  public readonly rightButton: boolean;\n  public readonly buttons: number;\n  public readonly target: HTMLElement;\n  public detail: number;\n  public readonly posx: number;\n  public readonly posy: number;\n  public readonly ctrlKey: boolean;\n  public readonly shiftKey: boolean;\n  public readonly altKey: boolean;\n  public readonly metaKey: boolean;\n  public readonly timestamp: number;\n\n  constructor(targetWindow: Window, e: MouseEvent) {\n    this.timestamp = Date.now();\n    this.browserEvent = e;\n    this.leftButton = e.button === 0;\n    this.middleButton = e.button === 1;\n    this.rightButton = e.button === 2;\n    this.buttons = e.buttons;\n\n    this.target = e.target as HTMLElement;\n\n    this.detail = e.detail ?? 1;\n    if (e.type === 'dblclick') {\n      this.detail = 2;\n    }\n    this.ctrlKey = e.ctrlKey;\n    this.shiftKey = e.shiftKey;\n    this.altKey = e.altKey;\n    this.metaKey = e.metaKey;\n\n    if (typeof e.pageX === 'number') {\n      this.posx = e.pageX;\n      this.posy = e.pageY;\n    } else {\n      this.posx = e.clientX + this.target.ownerDocument.body.scrollLeft + this.target.ownerDocument.documentElement.scrollLeft;\n      this.posy = e.clientY + this.target.ownerDocument.body.scrollTop + this.target.ownerDocument.documentElement.scrollTop;\n    }\n\n    const iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(targetWindow, e.view);\n    this.posx -= iframeOffsets.left;\n    this.posy -= iframeOffsets.top;\n  }\n\n  public preventDefault(): void {\n    this.browserEvent.preventDefault();\n  }\n\n  public stopPropagation(): void {\n    this.browserEvent.stopPropagation();\n  }\n}\n\nexport interface IMouseWheelEvent extends MouseEvent {\n  readonly wheelDelta: number;\n  readonly wheelDeltaX: number;\n  readonly wheelDeltaY: number;\n\n  readonly deltaX: number;\n  readonly deltaY: number;\n  readonly deltaZ: number;\n  readonly deltaMode: number;\n}\n\ninterface IWebKitMouseWheelEvent {\n  wheelDeltaY: number;\n  wheelDeltaX: number;\n}\n\ninterface IGeckoMouseWheelEvent {\n  HORIZONTAL_AXIS: number;\n  VERTICAL_AXIS: number;\n  axis: number;\n  detail: number;\n}\n\nexport class StandardWheelEvent {\n\n  public readonly browserEvent: IMouseWheelEvent | null;\n  public readonly deltaY: number;\n  public readonly deltaX: number;\n  public readonly target: Node | null;\n\n  constructor(e: IMouseWheelEvent | null, deltaX: number = 0, deltaY: number = 0) {\n\n    this.browserEvent = e ?? null;\n    this.target = e ? (e.target ?? (e as any).targetNode ?? e.srcElement ?? null) : null;\n\n    this.deltaY = deltaY;\n    this.deltaX = deltaX;\n\n    let shouldFactorDPR: boolean = false;\n    if (platform.isChrome) {\n      const chromeVersionMatch = navigator.userAgent.match(/Chrome\\/(\\d+)/);\n      const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1]) : 123;\n      shouldFactorDPR = chromeMajorVersion <= 122;\n    }\n\n    if (e) {\n      const e1 = e as IWebKitMouseWheelEvent as any;\n      const e2 = e as unknown as IGeckoMouseWheelEvent;\n      const devicePixelRatio = e.view?.devicePixelRatio ?? 1;\n\n      if (typeof e1.wheelDeltaY !== 'undefined') {\n        if (shouldFactorDPR) {\n          this.deltaY = e1.wheelDeltaY / (120 * devicePixelRatio);\n        } else {\n          this.deltaY = e1.wheelDeltaY / 120;\n        }\n      } else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) {\n        this.deltaY = -e2.detail / 3;\n      } else if (e.type === 'wheel') {\n        const ev = e as unknown as WheelEvent;\n\n        if (ev.deltaMode === ev.DOM_DELTA_LINE) {\n          if (platform.isFirefox && !platform.isMac) {\n            this.deltaY = -e.deltaY / 3;\n          } else {\n            this.deltaY = -e.deltaY;\n          }\n        } else {\n          this.deltaY = -e.deltaY / 40;\n        }\n      }\n\n      if (typeof e1.wheelDeltaX !== 'undefined') {\n        if (platform.isSafari && platform.isWindows) {\n          this.deltaX = -(e1.wheelDeltaX / 120);\n        } else if (shouldFactorDPR) {\n          this.deltaX = e1.wheelDeltaX / (120 * devicePixelRatio);\n        } else {\n          this.deltaX = e1.wheelDeltaX / 120;\n        }\n      } else if (typeof e2.HORIZONTAL_AXIS !== 'undefined' && e2.axis === e2.HORIZONTAL_AXIS) {\n        this.deltaX = -e.detail / 3;\n      } else if (e.type === 'wheel') {\n        const ev = e as unknown as WheelEvent;\n\n        if (ev.deltaMode === ev.DOM_DELTA_LINE) {\n          if (platform.isFirefox && !platform.isMac) {\n            this.deltaX = -e.deltaX / 3;\n          } else {\n            this.deltaX = -e.deltaX;\n          }\n        } else {\n          this.deltaX = -e.deltaX / 40;\n        }\n      }\n\n      if (this.deltaY === 0 && this.deltaX === 0 && e.wheelDelta) {\n        if (shouldFactorDPR) {\n          this.deltaY = e.wheelDelta / (120 * devicePixelRatio);\n        } else {\n          this.deltaY = e.wheelDelta / 120;\n        }\n      }\n    }\n  }\n\n  public preventDefault(): void {\n    this.browserEvent?.preventDefault();\n  }\n\n  public stopPropagation(): void {\n    this.browserEvent?.stopPropagation();\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/scrollable.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { Emitter, IEvent } from 'common/Event';\nimport { Disposable, IDisposable } from 'common/Lifecycle';\n\nexport const enum ScrollbarVisibility {\n  AUTO = 1,\n  HIDDEN = 2,\n  VISIBLE = 3\n}\n\nexport interface IScrollEvent {\n  inSmoothScrolling: boolean;\n\n  oldWidth: number;\n  oldScrollWidth: number;\n  oldScrollLeft: number;\n\n  width: number;\n  scrollWidth: number;\n  scrollLeft: number;\n\n  oldHeight: number;\n  oldScrollHeight: number;\n  oldScrollTop: number;\n\n  height: number;\n  scrollHeight: number;\n  scrollTop: number;\n\n  widthChanged: boolean;\n  scrollWidthChanged: boolean;\n  scrollLeftChanged: boolean;\n\n  heightChanged: boolean;\n  scrollHeightChanged: boolean;\n  scrollTopChanged: boolean;\n}\n\nexport class ScrollState implements IScrollDimensions, IScrollPosition {\n  private _scrollStateBrand: void = undefined;\n\n  public readonly rawScrollLeft: number;\n  public readonly rawScrollTop: number;\n\n  public readonly width: number;\n  public readonly scrollWidth: number;\n  public readonly scrollLeft: number;\n  public readonly height: number;\n  public readonly scrollHeight: number;\n  public readonly scrollTop: number;\n\n  constructor(\n    private readonly _forceIntegerValues: boolean,\n    width: number,\n    scrollWidth: number,\n    scrollLeft: number,\n    height: number,\n    scrollHeight: number,\n    scrollTop: number\n  ) {\n    if (this._forceIntegerValues) {\n      width = width | 0;\n      scrollWidth = scrollWidth | 0;\n      scrollLeft = scrollLeft | 0;\n      height = height | 0;\n      scrollHeight = scrollHeight | 0;\n      scrollTop = scrollTop | 0;\n    }\n\n    this.rawScrollLeft = scrollLeft;\n    this.rawScrollTop = scrollTop;\n\n    if (width < 0) {\n      width = 0;\n    }\n    if (scrollLeft + width > scrollWidth) {\n      scrollLeft = scrollWidth - width;\n    }\n    if (scrollLeft < 0) {\n      scrollLeft = 0;\n    }\n\n    if (height < 0) {\n      height = 0;\n    }\n    if (scrollTop + height > scrollHeight) {\n      scrollTop = scrollHeight - height;\n    }\n    if (scrollTop < 0) {\n      scrollTop = 0;\n    }\n\n    this.width = width;\n    this.scrollWidth = scrollWidth;\n    this.scrollLeft = scrollLeft;\n    this.height = height;\n    this.scrollHeight = scrollHeight;\n    this.scrollTop = scrollTop;\n  }\n\n  public equals(other: ScrollState): boolean {\n    return (\n      this.rawScrollLeft === other.rawScrollLeft\n\t\t\t&& this.rawScrollTop === other.rawScrollTop\n\t\t\t&& this.width === other.width\n\t\t\t&& this.scrollWidth === other.scrollWidth\n\t\t\t&& this.scrollLeft === other.scrollLeft\n\t\t\t&& this.height === other.height\n\t\t\t&& this.scrollHeight === other.scrollHeight\n\t\t\t&& this.scrollTop === other.scrollTop\n    );\n  }\n\n  public withScrollDimensions(update: INewScrollDimensions, useRawScrollPositions: boolean): ScrollState {\n    return new ScrollState(\n      this._forceIntegerValues,\n      (typeof update.width !== 'undefined' ? update.width : this.width),\n      (typeof update.scrollWidth !== 'undefined' ? update.scrollWidth : this.scrollWidth),\n      useRawScrollPositions ? this.rawScrollLeft : this.scrollLeft,\n      (typeof update.height !== 'undefined' ? update.height : this.height),\n      (typeof update.scrollHeight !== 'undefined' ? update.scrollHeight : this.scrollHeight),\n      useRawScrollPositions ? this.rawScrollTop : this.scrollTop\n    );\n  }\n\n  public withScrollPosition(update: INewScrollPosition): ScrollState {\n    return new ScrollState(\n      this._forceIntegerValues,\n      this.width,\n      this.scrollWidth,\n      (typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.rawScrollLeft),\n      this.height,\n      this.scrollHeight,\n      (typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.rawScrollTop)\n    );\n  }\n\n  public createScrollEvent(previous: ScrollState, inSmoothScrolling: boolean): IScrollEvent {\n    const widthChanged = (this.width !== previous.width);\n    const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth);\n    const scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft);\n\n    const heightChanged = (this.height !== previous.height);\n    const scrollHeightChanged = (this.scrollHeight !== previous.scrollHeight);\n    const scrollTopChanged = (this.scrollTop !== previous.scrollTop);\n\n    return {\n      inSmoothScrolling: inSmoothScrolling,\n      oldWidth: previous.width,\n      oldScrollWidth: previous.scrollWidth,\n      oldScrollLeft: previous.scrollLeft,\n\n      width: this.width,\n      scrollWidth: this.scrollWidth,\n      scrollLeft: this.scrollLeft,\n\n      oldHeight: previous.height,\n      oldScrollHeight: previous.scrollHeight,\n      oldScrollTop: previous.scrollTop,\n\n      height: this.height,\n      scrollHeight: this.scrollHeight,\n      scrollTop: this.scrollTop,\n\n      widthChanged: widthChanged,\n      scrollWidthChanged: scrollWidthChanged,\n      scrollLeftChanged: scrollLeftChanged,\n\n      heightChanged: heightChanged,\n      scrollHeightChanged: scrollHeightChanged,\n      scrollTopChanged: scrollTopChanged,\n    };\n  }\n\n}\n\nexport interface IScrollDimensions {\n  readonly width: number;\n  readonly scrollWidth: number;\n  readonly height: number;\n  readonly scrollHeight: number;\n}\nexport interface INewScrollDimensions {\n  width?: number;\n  scrollWidth?: number;\n  height?: number;\n  scrollHeight?: number;\n}\n\nexport interface IScrollPosition {\n  readonly scrollLeft: number;\n  readonly scrollTop: number;\n}\nexport interface ISmoothScrollPosition {\n  readonly scrollLeft: number;\n  readonly scrollTop: number;\n\n  readonly width: number;\n  readonly height: number;\n}\nexport interface INewScrollPosition {\n  scrollLeft?: number;\n  scrollTop?: number;\n}\n\nexport interface IScrollableOptions {\n  forceIntegerValues: boolean;\n  smoothScrollDuration: number;\n  scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable;\n}\n\nexport class Scrollable extends Disposable {\n\n  private _scrollableBrand: void = undefined;\n\n  private _smoothScrollDuration: number;\n  private readonly _scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable;\n  private _state: ScrollState;\n  private _smoothScrolling: SmoothScrollingOperation | null;\n\n  private _onScroll = this._register(new Emitter<IScrollEvent>());\n  public readonly onScroll: IEvent<IScrollEvent> = this._onScroll.event;\n\n  constructor(options: IScrollableOptions) {\n    super();\n\n    this._smoothScrollDuration = options.smoothScrollDuration;\n    this._scheduleAtNextAnimationFrame = options.scheduleAtNextAnimationFrame;\n    this._state = new ScrollState(options.forceIntegerValues, 0, 0, 0, 0, 0, 0);\n    this._smoothScrolling = null;\n  }\n\n  public override dispose(): void {\n    if (this._smoothScrolling) {\n      this._smoothScrolling.dispose();\n      this._smoothScrolling = null;\n    }\n    super.dispose();\n  }\n\n  public setSmoothScrollDuration(smoothScrollDuration: number): void {\n    this._smoothScrollDuration = smoothScrollDuration;\n  }\n\n  public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition {\n    return this._state.withScrollPosition(scrollPosition);\n  }\n\n  public getScrollDimensions(): IScrollDimensions {\n    return this._state;\n  }\n\n  public setScrollDimensions(dimensions: INewScrollDimensions, useRawScrollPositions: boolean): void {\n    const newState = this._state.withScrollDimensions(dimensions, useRawScrollPositions);\n    this._setState(newState, Boolean(this._smoothScrolling));\n\n    this._smoothScrolling?.acceptScrollDimensions(this._state);\n  }\n\n  public getFutureScrollPosition(): IScrollPosition {\n    if (this._smoothScrolling) {\n      return this._smoothScrolling.to;\n    }\n    return this._state;\n  }\n\n  public getCurrentScrollPosition(): IScrollPosition {\n    return this._state;\n  }\n\n  public setScrollPositionNow(update: INewScrollPosition): void {\n    const newState = this._state.withScrollPosition(update);\n\n    if (this._smoothScrolling) {\n      this._smoothScrolling.dispose();\n      this._smoothScrolling = null;\n    }\n\n    this._setState(newState, false);\n  }\n\n  public setScrollPositionSmooth(update: INewScrollPosition, reuseAnimation?: boolean): void {\n    if (this._smoothScrollDuration === 0) {\n      this.setScrollPositionNow(update); return;\n    }\n\n    if (this._smoothScrolling) {\n      update = {\n        scrollLeft: (typeof update.scrollLeft === 'undefined' ? this._smoothScrolling.to.scrollLeft : update.scrollLeft),\n        scrollTop: (typeof update.scrollTop === 'undefined' ? this._smoothScrolling.to.scrollTop : update.scrollTop)\n      };\n\n      const validTarget = this._state.withScrollPosition(update);\n\n      if (this._smoothScrolling.to.scrollLeft === validTarget.scrollLeft && this._smoothScrolling.to.scrollTop === validTarget.scrollTop) {\n        return;\n      }\n      let newSmoothScrolling: SmoothScrollingOperation;\n      if (reuseAnimation) {\n        newSmoothScrolling = new SmoothScrollingOperation(this._smoothScrolling.from, validTarget, this._smoothScrolling.startTime, this._smoothScrolling.duration);\n      } else {\n        newSmoothScrolling = SmoothScrollingOperation.start(this._state, validTarget, this._smoothScrollDuration);\n      }\n      this._smoothScrolling.dispose();\n      this._smoothScrolling = newSmoothScrolling;\n    } else {\n      const validTarget = this._state.withScrollPosition(update);\n\n      this._smoothScrolling = SmoothScrollingOperation.start(this._state, validTarget, this._smoothScrollDuration);\n    }\n\n    this._smoothScrolling.animationFrameDisposable = this._scheduleAtNextAnimationFrame(() => {\n      if (!this._smoothScrolling) {\n        return;\n      }\n      this._smoothScrolling.animationFrameDisposable = null;\n      this._performSmoothScrolling();\n    });\n  }\n\n  public hasPendingScrollAnimation(): boolean {\n    return Boolean(this._smoothScrolling);\n  }\n\n  private _performSmoothScrolling(): void {\n    if (!this._smoothScrolling) {\n      return;\n    }\n    const update = this._smoothScrolling.tick();\n    const newState = this._state.withScrollPosition(update);\n\n    this._setState(newState, true);\n\n    if (!this._smoothScrolling) {\n      return;\n    }\n\n    if (update.isDone) {\n      this._smoothScrolling.dispose();\n      this._smoothScrolling = null;\n      return;\n    }\n\n    this._smoothScrolling.animationFrameDisposable = this._scheduleAtNextAnimationFrame(() => {\n      if (!this._smoothScrolling) {\n        return;\n      }\n      this._smoothScrolling.animationFrameDisposable = null;\n      this._performSmoothScrolling();\n    });\n  }\n\n  private _setState(newState: ScrollState, inSmoothScrolling: boolean): void {\n    const oldState = this._state;\n    if (oldState.equals(newState)) {\n      return;\n    }\n    this._state = newState;\n    this._onScroll.fire(this._state.createScrollEvent(oldState, inSmoothScrolling));\n  }\n}\n\nclass SmoothScrollingUpdate {\n\n  public readonly scrollLeft: number;\n  public readonly scrollTop: number;\n  public readonly isDone: boolean;\n\n  constructor(scrollLeft: number, scrollTop: number, isDone: boolean) {\n    this.scrollLeft = scrollLeft;\n    this.scrollTop = scrollTop;\n    this.isDone = isDone;\n  }\n\n}\n\ninterface IAnimation {\n  (completion: number): number;\n}\n\nfunction createEaseOutCubic(from: number, to: number): IAnimation {\n  const delta = to - from;\n  return function (completion: number): number {\n    return from + delta * easeOutCubic(completion);\n  };\n}\n\nfunction createComposed(a: IAnimation, b: IAnimation, cut: number): IAnimation {\n  return function (completion: number): number {\n    if (completion < cut) {\n      return a(completion / cut);\n    }\n    return b((completion - cut) / (1 - cut));\n  };\n}\n\nclass SmoothScrollingOperation {\n\n  public readonly from: ISmoothScrollPosition;\n  public to: ISmoothScrollPosition;\n  public readonly duration: number;\n  public readonly startTime: number;\n  public animationFrameDisposable: IDisposable | null;\n\n  private _scrollLeft!: IAnimation;\n  private _scrollTop!: IAnimation;\n\n  constructor(from: ISmoothScrollPosition, to: ISmoothScrollPosition, startTime: number, duration: number) {\n    this.from = from;\n    this.to = to;\n    this.duration = duration;\n    this.startTime = startTime;\n\n    this.animationFrameDisposable = null;\n\n    this._initAnimations();\n  }\n\n  private _initAnimations(): void {\n    this._scrollLeft = this._initAnimation(this.from.scrollLeft, this.to.scrollLeft, this.to.width);\n    this._scrollTop = this._initAnimation(this.from.scrollTop, this.to.scrollTop, this.to.height);\n  }\n\n  private _initAnimation(from: number, to: number, viewportSize: number): IAnimation {\n    const delta = Math.abs(from - to);\n    if (delta > 2.5 * viewportSize) {\n      let stop1: number; let stop2: number;\n      if (from < to) {\n        stop1 = from + 0.75 * viewportSize;\n        stop2 = to - 0.75 * viewportSize;\n      } else {\n        stop1 = from - 0.75 * viewportSize;\n        stop2 = to + 0.75 * viewportSize;\n      }\n      return createComposed(createEaseOutCubic(from, stop1), createEaseOutCubic(stop2, to), 0.33);\n    }\n    return createEaseOutCubic(from, to);\n  }\n\n  public dispose(): void {\n    if (this.animationFrameDisposable !== null) {\n      this.animationFrameDisposable.dispose();\n      this.animationFrameDisposable = null;\n    }\n  }\n\n  public acceptScrollDimensions(state: ScrollState): void {\n    this.to = state.withScrollPosition(this.to);\n    this._initAnimations();\n  }\n\n  public tick(): SmoothScrollingUpdate {\n    return this._tick(Date.now());\n  }\n\n  protected _tick(now: number): SmoothScrollingUpdate {\n    const completion = (now - this.startTime) / this.duration;\n\n    if (completion < 1) {\n      const newScrollLeft = this._scrollLeft(completion);\n      const newScrollTop = this._scrollTop(completion);\n      return new SmoothScrollingUpdate(newScrollLeft, newScrollTop, false);\n    }\n\n    return new SmoothScrollingUpdate(this.to.scrollLeft, this.to.scrollTop, true);\n  }\n\n  public static start(from: ISmoothScrollPosition, to: ISmoothScrollPosition, duration: number): SmoothScrollingOperation {\n    duration = duration + 10;\n    const startTime = Date.now() - 10;\n\n    return new SmoothScrollingOperation(from, to, startTime, duration);\n  }\n}\n\nfunction easeInCubic(t: number): number {\n  return Math.pow(t, 3);\n}\n\nfunction easeOutCubic(t: number): number {\n  return 1 - easeInCubic(1 - t);\n}\n"
  },
  {
    "path": "src/browser/scrollable/scrollableElement.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as dom from '../Dom';\nimport { FastDomNode } from './fastDomNode';\nimport { IMouseEvent, IMouseWheelEvent, StandardWheelEvent } from './mouseEvent';\nimport { IScrollbarHost } from './abstractScrollbar';\nimport { HorizontalScrollbar } from './horizontalScrollbar';\nimport { IScrollableElementChangeOptions, IScrollableElementCreationOptions, IScrollableElementResolvedOptions } from './scrollableElementOptions';\nimport { VerticalScrollbar } from './verticalScrollbar';\nimport { Widget } from './widget';\nimport { TimeoutTimer } from 'common/Async';\nimport { Emitter, IEvent } from 'common/Event';\nimport { IDisposable, dispose } from 'common/Lifecycle';\nimport * as platform from 'common/Platform';\nimport { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, IScrollEvent, Scrollable, ScrollbarVisibility } from './scrollable';\n// import 'vs/css!./media/scrollbars';\n\nconst HIDE_TIMEOUT = 500;\nconst SCROLL_WHEEL_SENSITIVITY = 50;\n\nclass MouseWheelClassifierItem {\n  public timestamp: number;\n  public deltaX: number;\n  public deltaY: number;\n  public score: number;\n\n  constructor(timestamp: number, deltaX: number, deltaY: number) {\n    this.timestamp = timestamp;\n    this.deltaX = deltaX;\n    this.deltaY = deltaY;\n    this.score = 0;\n  }\n}\n\nclass MouseWheelClassifier {\n\n  public static readonly INSTANCE = new MouseWheelClassifier();\n\n  private readonly _capacity: number;\n  private _memory: MouseWheelClassifierItem[];\n  private _front: number;\n  private _rear: number;\n\n  constructor() {\n    this._capacity = 5;\n    this._memory = [];\n    this._front = -1;\n    this._rear = -1;\n  }\n\n  public isPhysicalMouseWheel(): boolean {\n    if (this._front === -1 && this._rear === -1) {\n      return false;\n    }\n\n    let remainingInfluence = 1;\n    let score = 0;\n    let iteration = 1;\n\n    let index = this._rear;\n    while (index !== -1) {\n      const influence = (index === this._front ? remainingInfluence : Math.pow(2, -iteration));\n      remainingInfluence -= influence;\n      score += this._memory[index].score * influence;\n\n      if (index === this._front) {\n        break;\n      }\n\n      index = (this._capacity + index - 1) % this._capacity;\n      iteration++;\n    }\n\n    return (score <= 0.5);\n  }\n\n  public acceptStandardWheelEvent(e: StandardWheelEvent): void {\n    if (platform.isChrome) {\n      const targetWindow = dom.getWindow(e.browserEvent);\n      const pageZoomFactor = platform.getZoomFactor(targetWindow);\n      this.accept(Date.now(), e.deltaX * pageZoomFactor, e.deltaY * pageZoomFactor);\n    } else {\n      this.accept(Date.now(), e.deltaX, e.deltaY);\n    }\n  }\n\n  public accept(timestamp: number, deltaX: number, deltaY: number): void {\n    let previousItem = null;\n    const item = new MouseWheelClassifierItem(timestamp, deltaX, deltaY);\n\n    if (this._front === -1 && this._rear === -1) {\n      this._memory[0] = item;\n      this._front = 0;\n      this._rear = 0;\n    } else {\n      previousItem = this._memory[this._rear];\n\n      this._rear = (this._rear + 1) % this._capacity;\n      if (this._rear === this._front) {\n        this._front = (this._front + 1) % this._capacity;\n      }\n      this._memory[this._rear] = item;\n    }\n\n    item.score = this._computeScore(item, previousItem);\n  }\n\n  private _computeScore(item: MouseWheelClassifierItem, previousItem: MouseWheelClassifierItem | null): number {\n\n    if (Math.abs(item.deltaX) > 0 && Math.abs(item.deltaY) > 0) {\n      return 1;\n    }\n\n    let score: number = 0.5;\n\n    if (!this._isAlmostInt(item.deltaX) || !this._isAlmostInt(item.deltaY)) {\n      score += 0.25;\n    }\n\n    if (previousItem) {\n      const absDeltaX = Math.abs(item.deltaX);\n      const absDeltaY = Math.abs(item.deltaY);\n\n      const absPreviousDeltaX = Math.abs(previousItem.deltaX);\n      const absPreviousDeltaY = Math.abs(previousItem.deltaY);\n\n      const minDeltaX = Math.max(Math.min(absDeltaX, absPreviousDeltaX), 1);\n      const minDeltaY = Math.max(Math.min(absDeltaY, absPreviousDeltaY), 1);\n\n      const maxDeltaX = Math.max(absDeltaX, absPreviousDeltaX);\n      const maxDeltaY = Math.max(absDeltaY, absPreviousDeltaY);\n\n      const isSameModulo = (maxDeltaX % minDeltaX === 0 && maxDeltaY % minDeltaY === 0);\n      if (isSameModulo) {\n        score -= 0.5;\n      }\n    }\n\n    return Math.min(Math.max(score, 0), 1);\n  }\n\n  private _isAlmostInt(value: number): boolean {\n    const delta = Math.abs(Math.round(value) - value);\n    return (delta < 0.01);\n  }\n}\n\nexport class SmoothScrollableElement extends Widget {\n\n  private readonly _options: IScrollableElementResolvedOptions;\n  protected readonly _scrollable: Scrollable;\n  private readonly _verticalScrollbar: VerticalScrollbar;\n  private readonly _horizontalScrollbar: HorizontalScrollbar;\n  private readonly _domNode: HTMLElement;\n\n  private readonly _leftShadowDomNode: FastDomNode<HTMLElement> | null;\n  private readonly _topShadowDomNode: FastDomNode<HTMLElement> | null;\n  private readonly _topLeftShadowDomNode: FastDomNode<HTMLElement> | null;\n\n  private readonly _listenOnDomNode: HTMLElement;\n\n  private _mouseWheelToDispose: IDisposable[];\n\n  private _isDragging: boolean;\n  private _mouseIsOver: boolean;\n\n  private readonly _hideTimeout: TimeoutTimer;\n  private _shouldRender: boolean;\n\n  private _revealOnScroll: boolean;\n\n  private readonly _onScroll = this._register(new Emitter<IScrollEvent>());\n  public readonly onScroll: IEvent<IScrollEvent> = this._onScroll.event;\n\n  public get options(): Readonly<IScrollableElementResolvedOptions> {\n    return this._options;\n  }\n\n  public constructor(element: HTMLElement, options: IScrollableElementCreationOptions, scrollable?: Scrollable) {\n    super();\n    options = options ?? {};\n    let resolvedScrollable: Scrollable;\n    const ownsScrollable = !scrollable;\n    if (scrollable) {\n      resolvedScrollable = scrollable;\n    } else {\n      options.mouseWheelSmoothScroll = false;\n      resolvedScrollable = new Scrollable({\n        forceIntegerValues: true,\n        smoothScrollDuration: 0,\n        scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(dom.getWindow(element), callback)\n      });\n    }\n\n    this._options = resolveOptions(options);\n    this._scrollable = resolvedScrollable;\n\n    this._register(this._scrollable.onScroll((e) => {\n      this._handleScroll(e);\n      this._onScroll.fire(e);\n    }));\n    if (ownsScrollable) {\n      this._register(this._scrollable);\n    }\n\n    const scrollbarHost: IScrollbarHost = {\n      handleMouseWheel: (mouseWheelEvent: StandardWheelEvent) => this._handleMouseWheel(mouseWheelEvent),\n      handleDragStart: () => this._handleDragStart(),\n      handleDragEnd: () => this._handleDragEnd(),\n    };\n    this._verticalScrollbar = this._register(new VerticalScrollbar(this._scrollable, this._options, scrollbarHost));\n    this._horizontalScrollbar = this._register(new HorizontalScrollbar(this._scrollable, this._options, scrollbarHost));\n\n    this._domNode = document.createElement('div');\n    this._domNode.className = 'xterm-scrollable-element ' + this._options.className;\n    this._domNode.setAttribute('role', 'presentation');\n    this._domNode.style.position = 'relative';\n    this._domNode.appendChild(element);\n    this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode);\n    this._domNode.appendChild(this._verticalScrollbar.domNode.domNode);\n\n    if (this._options.useShadows) {\n      this._leftShadowDomNode = new FastDomNode(document.createElement('div'));\n      this._leftShadowDomNode.setClassName('xterm-shadow');\n      this._domNode.appendChild(this._leftShadowDomNode.domNode);\n\n      this._topShadowDomNode = new FastDomNode(document.createElement('div'));\n      this._topShadowDomNode.setClassName('xterm-shadow');\n      this._domNode.appendChild(this._topShadowDomNode.domNode);\n\n      this._topLeftShadowDomNode = new FastDomNode(document.createElement('div'));\n      this._topLeftShadowDomNode.setClassName('xterm-shadow');\n      this._domNode.appendChild(this._topLeftShadowDomNode.domNode);\n    } else {\n      this._leftShadowDomNode = null;\n      this._topShadowDomNode = null;\n      this._topLeftShadowDomNode = null;\n    }\n\n    this._listenOnDomNode = this._options.listenOnDomNode ?? this._domNode;\n\n    this._mouseWheelToDispose = [];\n    this._setListeningToMouseWheel(this._options.handleMouseWheel);\n\n    this._onmouseover(this._listenOnDomNode, (e) => this._handleMouseOver(e));\n    this._onmouseleave(this._listenOnDomNode, (e) => this._handleMouseLeave(e));\n\n    this._hideTimeout = this._register(new TimeoutTimer());\n    this._isDragging = false;\n    this._mouseIsOver = false;\n\n    this._shouldRender = true;\n\n    this._revealOnScroll = true;\n  }\n\n  public override dispose(): void {\n    this._mouseWheelToDispose = dispose(this._mouseWheelToDispose);\n    super.dispose();\n  }\n\n  public getDomNode(): HTMLElement {\n    return this._domNode;\n  }\n\n  public getScrollDimensions(): IScrollDimensions {\n    return this._scrollable.getScrollDimensions();\n  }\n\n  public setScrollDimensions(dimensions: INewScrollDimensions): void {\n    this._scrollable.setScrollDimensions(dimensions, false);\n  }\n\n  public setScrollPosition(update: INewScrollPosition & { reuseAnimation?: boolean }): void {\n    if (update.reuseAnimation) {\n      this._scrollable.setScrollPositionSmooth(update, update.reuseAnimation);\n    } else {\n      this._scrollable.setScrollPositionNow(update);\n    }\n  }\n\n  public getScrollPosition(): IScrollPosition {\n    return this._scrollable.getCurrentScrollPosition();\n  }\n\n  public updateClassName(newClassName: string): void {\n    this._options.className = newClassName;\n    if (platform.isMac) {\n      this._options.className += ' xterm-mac';\n    }\n    this._domNode.className = 'xterm-scrollable-element ' + this._options.className;\n  }\n\n  public updateOptions(newOptions: IScrollableElementChangeOptions): void {\n    if (typeof newOptions.handleMouseWheel !== 'undefined') {\n      this._options.handleMouseWheel = newOptions.handleMouseWheel;\n      this._setListeningToMouseWheel(this._options.handleMouseWheel);\n    }\n    if (typeof newOptions.mouseWheelScrollSensitivity !== 'undefined') {\n      this._options.mouseWheelScrollSensitivity = newOptions.mouseWheelScrollSensitivity;\n    }\n    if (typeof newOptions.fastScrollSensitivity !== 'undefined') {\n      this._options.fastScrollSensitivity = newOptions.fastScrollSensitivity;\n    }\n    if (typeof newOptions.scrollPredominantAxis !== 'undefined') {\n      this._options.scrollPredominantAxis = newOptions.scrollPredominantAxis;\n    }\n    if (typeof newOptions.horizontal !== 'undefined') {\n      this._options.horizontal = newOptions.horizontal;\n    }\n    if (typeof newOptions.vertical !== 'undefined') {\n      this._options.vertical = newOptions.vertical;\n    }\n    if (typeof newOptions.horizontalHasArrows !== 'undefined') {\n      this._options.horizontalHasArrows = newOptions.horizontalHasArrows;\n    }\n    if (typeof newOptions.verticalHasArrows !== 'undefined') {\n      this._options.verticalHasArrows = newOptions.verticalHasArrows;\n    }\n    if (typeof newOptions.horizontalScrollbarSize !== 'undefined') {\n      this._options.horizontalScrollbarSize = newOptions.horizontalScrollbarSize;\n    }\n    if (typeof newOptions.verticalScrollbarSize !== 'undefined') {\n      this._options.verticalScrollbarSize = newOptions.verticalScrollbarSize;\n    }\n    if (typeof newOptions.scrollByPage !== 'undefined') {\n      this._options.scrollByPage = newOptions.scrollByPage;\n    }\n    this._horizontalScrollbar.updateOptions(this._options);\n    this._verticalScrollbar.updateOptions(this._options);\n\n    if (!this._options.lazyRender) {\n      this._render();\n    }\n  }\n\n  public delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void {\n    this._handleMouseWheel(new StandardWheelEvent(browserEvent));\n  }\n\n  // -------------------- mouse wheel scrolling --------------------\n\n  private _setListeningToMouseWheel(shouldListen: boolean): void {\n    const isListening = (this._mouseWheelToDispose.length > 0);\n\n    if (isListening === shouldListen) {\n      return;\n    }\n\n    this._mouseWheelToDispose = dispose(this._mouseWheelToDispose);\n\n    if (shouldListen) {\n      const onMouseWheel = (browserEvent: IMouseWheelEvent): void => {\n        this._handleMouseWheel(new StandardWheelEvent(browserEvent));\n      };\n\n      this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, dom.eventType.MOUSE_WHEEL, onMouseWheel, { passive: false }));\n    }\n  }\n\n  private _handleMouseWheel(e: StandardWheelEvent): void {\n    if (e.browserEvent?.defaultPrevented) {\n      return;\n    }\n\n    const classifier = MouseWheelClassifier.INSTANCE;\n    classifier.acceptStandardWheelEvent(e);\n\n    let didScroll = false;\n\n    if (e.deltaY || e.deltaX) {\n      let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity;\n      let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity;\n\n      if (this._options.scrollPredominantAxis) {\n        if (this._options.scrollYToX && deltaX + deltaY === 0) {\n          deltaX = deltaY = 0;\n        } else if (Math.abs(deltaY) >= Math.abs(deltaX)) {\n          deltaX = 0;\n        } else {\n          deltaY = 0;\n        }\n      }\n\n      if (this._options.flipAxes) {\n        [deltaY, deltaX] = [deltaX, deltaY];\n      }\n\n      const shiftConvert = !platform.isMac && e.browserEvent && e.browserEvent.shiftKey;\n      if ((this._options.scrollYToX || shiftConvert) && !deltaX) {\n        deltaX = deltaY;\n        deltaY = 0;\n      }\n\n      if (e.browserEvent && e.browserEvent.altKey) {\n        deltaX = deltaX * this._options.fastScrollSensitivity;\n        deltaY = deltaY * this._options.fastScrollSensitivity;\n      }\n\n      const futureScrollPosition = this._scrollable.getFutureScrollPosition();\n\n      let desiredScrollPosition: INewScrollPosition = {};\n      if (deltaY) {\n        const deltaScrollTop = SCROLL_WHEEL_SENSITIVITY * deltaY;\n        const desiredScrollTop = futureScrollPosition.scrollTop - (deltaScrollTop < 0 ? Math.floor(deltaScrollTop) : Math.ceil(deltaScrollTop));\n        this._verticalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollTop);\n      }\n      if (deltaX) {\n        const deltaScrollLeft = SCROLL_WHEEL_SENSITIVITY * deltaX;\n        const desiredScrollLeft = futureScrollPosition.scrollLeft - (deltaScrollLeft < 0 ? Math.floor(deltaScrollLeft) : Math.ceil(deltaScrollLeft));\n        this._horizontalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollLeft);\n      }\n\n      desiredScrollPosition = this._scrollable.validateScrollPosition(desiredScrollPosition);\n\n      if (futureScrollPosition.scrollLeft !== desiredScrollPosition.scrollLeft || futureScrollPosition.scrollTop !== desiredScrollPosition.scrollTop) {\n\n        const canPerformSmoothScroll = (\n          this._options.mouseWheelSmoothScroll\n\t\t\t\t\t&& classifier.isPhysicalMouseWheel()\n        );\n\n        if (canPerformSmoothScroll) {\n          this._scrollable.setScrollPositionSmooth(desiredScrollPosition);\n        } else {\n          this._scrollable.setScrollPositionNow(desiredScrollPosition);\n        }\n\n        didScroll = true;\n      }\n    }\n\n    let consumeMouseWheel = didScroll;\n    if (!consumeMouseWheel && this._options.alwaysConsumeMouseWheel) {\n      consumeMouseWheel = true;\n    }\n    if (!consumeMouseWheel && this._options.consumeMouseWheelIfScrollbarIsNeeded && (this._verticalScrollbar.isNeeded() || this._horizontalScrollbar.isNeeded())) {\n      consumeMouseWheel = true;\n    }\n\n    if (consumeMouseWheel) {\n      e.preventDefault();\n      e.stopPropagation();\n    }\n  }\n\n  private _handleScroll(e: IScrollEvent): void {\n    this._shouldRender = this._horizontalScrollbar.handleScroll(e) || this._shouldRender;\n    this._shouldRender = this._verticalScrollbar.handleScroll(e) || this._shouldRender;\n\n    if (this._options.useShadows) {\n      this._shouldRender = true;\n    }\n\n    if (this._revealOnScroll) {\n      this._reveal();\n    }\n\n    if (!this._options.lazyRender) {\n      this._render();\n    }\n  }\n\n  public renderNow(): void {\n    if (!this._options.lazyRender) {\n      throw new Error('Please use `lazyRender` together with `renderNow`!');\n    }\n\n    this._render();\n  }\n\n  private _render(): void {\n    if (!this._shouldRender) {\n      return;\n    }\n\n    this._shouldRender = false;\n\n    this._horizontalScrollbar.render();\n    this._verticalScrollbar.render();\n\n    if (this._options.useShadows) {\n      const scrollState = this._scrollable.getCurrentScrollPosition();\n      const enableTop = scrollState.scrollTop > 0;\n      const enableLeft = scrollState.scrollLeft > 0;\n\n      const leftClassName = (enableLeft ? ' xterm-shadow-left' : '');\n      const topClassName = (enableTop ? ' xterm-shadow-top' : '');\n      const topLeftClassName = (enableLeft || enableTop ? ' xterm-shadow-top-left-corner' : '');\n      this._leftShadowDomNode!.setClassName(`xterm-shadow${leftClassName}`);\n      this._topShadowDomNode!.setClassName(`xterm-shadow${topClassName}`);\n      this._topLeftShadowDomNode!.setClassName(`xterm-shadow${topLeftClassName}${topClassName}${leftClassName}`);\n    }\n  }\n\n  // -------------------- fade in / fade out --------------------\n\n  private _handleDragStart(): void {\n    this._isDragging = true;\n    this._reveal();\n  }\n\n  private _handleDragEnd(): void {\n    this._isDragging = false;\n    this._hide();\n  }\n\n  private _handleMouseLeave(e: IMouseEvent): void {\n    this._mouseIsOver = false;\n    this._hide();\n  }\n\n  private _handleMouseOver(e: IMouseEvent): void {\n    this._mouseIsOver = true;\n    this._reveal();\n  }\n\n  private _reveal(): void {\n    this._verticalScrollbar.beginReveal();\n    this._horizontalScrollbar.beginReveal();\n    this._scheduleHide();\n  }\n\n  private _hide(): void {\n    if (!this._mouseIsOver && !this._isDragging) {\n      this._verticalScrollbar.beginHide();\n      this._horizontalScrollbar.beginHide();\n    }\n  }\n\n  private _scheduleHide(): void {\n    if (!this._mouseIsOver && !this._isDragging) {\n      this._hideTimeout.cancelAndSet(() => this._hide(), HIDE_TIMEOUT);\n    }\n  }\n}\n\nfunction resolveOptions(opts: IScrollableElementCreationOptions): IScrollableElementResolvedOptions {\n  const result: IScrollableElementResolvedOptions = {\n    lazyRender: (typeof opts.lazyRender !== 'undefined' ? opts.lazyRender : false),\n    className: (typeof opts.className !== 'undefined' ? opts.className : ''),\n    useShadows: (typeof opts.useShadows !== 'undefined' ? opts.useShadows : true),\n    handleMouseWheel: (typeof opts.handleMouseWheel !== 'undefined' ? opts.handleMouseWheel : true),\n    flipAxes: (typeof opts.flipAxes !== 'undefined' ? opts.flipAxes : false),\n    consumeMouseWheelIfScrollbarIsNeeded: (typeof opts.consumeMouseWheelIfScrollbarIsNeeded !== 'undefined' ? opts.consumeMouseWheelIfScrollbarIsNeeded : false),\n    alwaysConsumeMouseWheel: (typeof opts.alwaysConsumeMouseWheel !== 'undefined' ? opts.alwaysConsumeMouseWheel : false),\n    scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false),\n    mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),\n    fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5),\n    scrollPredominantAxis: (typeof opts.scrollPredominantAxis !== 'undefined' ? opts.scrollPredominantAxis : true),\n    mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true),\n\n    listenOnDomNode: (typeof opts.listenOnDomNode !== 'undefined' ? opts.listenOnDomNode : null),\n\n    horizontal: (typeof opts.horizontal !== 'undefined' ? opts.horizontal : ScrollbarVisibility.AUTO),\n    horizontalScrollbarSize: (typeof opts.horizontalScrollbarSize !== 'undefined' ? opts.horizontalScrollbarSize : 10),\n    horizontalSliderSize: (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : 0),\n    horizontalHasArrows: (typeof opts.horizontalHasArrows !== 'undefined' ? opts.horizontalHasArrows : false),\n\n    vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : ScrollbarVisibility.AUTO),\n    verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10),\n    verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false),\n    verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0),\n\n    scrollByPage: (typeof opts.scrollByPage !== 'undefined' ? opts.scrollByPage : false)\n  };\n\n  result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize);\n  result.verticalSliderSize = (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : result.verticalScrollbarSize);\n\n  if (platform.isMac) {\n    result.className += ' xterm-mac';\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "src/browser/scrollable/scrollableElementOptions.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { ScrollbarVisibility } from './scrollable';\n\nexport interface IScrollableElementCreationOptions {\n  /**\n   * The scrollable element should not do any DOM mutations until renderNow() is called.\n   * Defaults to false.\n   */\n  lazyRender?: boolean;\n  /**\n   * CSS Class name for the scrollable element.\n   */\n  className?: string;\n  /**\n   * Drop subtle horizontal and vertical shadows.\n   * Defaults to false.\n   */\n  useShadows?: boolean;\n  /**\n   * Handle mouse wheel (listen to mouse wheel scrolling).\n   * Defaults to true\n   */\n  handleMouseWheel?: boolean;\n  /**\n   * If mouse wheel is handled, make mouse wheel scrolling smooth.\n   * Defaults to true.\n   */\n  mouseWheelSmoothScroll?: boolean;\n  /**\n   * Flip axes. Treat vertical scrolling like horizontal and vice-versa.\n   * Defaults to false.\n   */\n  flipAxes?: boolean;\n  /**\n   * If enabled, will scroll horizontally when scrolling vertical.\n   * Defaults to false.\n   */\n  scrollYToX?: boolean;\n  /**\n   * Consume all mouse wheel events if a scrollbar is needed (i.e. scrollSize > size).\n   * Defaults to false.\n   */\n  consumeMouseWheelIfScrollbarIsNeeded?: boolean;\n  /**\n   * Always consume mouse wheel events, even when scrolling is no longer possible.\n   * Defaults to false.\n   */\n  alwaysConsumeMouseWheel?: boolean;\n  /**\n   * A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.\n   * Defaults to 1.\n   */\n  mouseWheelScrollSensitivity?: number;\n  /**\n   * FastScrolling mulitplier speed when pressing `Alt`\n   * Defaults to 5.\n   */\n  fastScrollSensitivity?: number;\n  /**\n   * Whether the scrollable will only scroll along the predominant axis when scrolling both\n   * vertically and horizontally at the same time.\n   * Prevents horizontal drift when scrolling vertically on a trackpad.\n   * Defaults to true.\n   */\n  scrollPredominantAxis?: boolean;\n  /**\n   * The dom node events should be bound to.\n   * If no listenOnDomNode is provided, the constructor dom node is used.\n   */\n  listenOnDomNode?: HTMLElement;\n  /**\n   * Control the visibility of the horizontal scrollbar.\n   * Accepted values: 'auto' (on mouse over), 'visible' (always visible), 'hidden' (never visible)\n   * Defaults to 'auto'.\n   */\n  horizontal?: ScrollbarVisibility;\n  /**\n   * Height (in px) of the horizontal scrollbar.\n   * Defaults to 10.\n   */\n  horizontalScrollbarSize?: number;\n  /**\n   * Height (in px) of the horizontal scrollbar slider.\n   * Defaults to `horizontalScrollbarSize`\n   */\n  horizontalSliderSize?: number;\n  /**\n   * Render arrows (left/right) for the horizontal scrollbar.\n   * Defaults to false.\n   */\n  horizontalHasArrows?: boolean;\n  /**\n   * Control the visibility of the vertical scrollbar.\n   * Accepted values: 'auto' (on mouse over), 'visible' (always visible), 'hidden' (never visible)\n   * Defaults to 'auto'.\n   */\n  vertical?: ScrollbarVisibility;\n  /**\n   * Width (in px) of the vertical scrollbar.\n   * Defaults to 10.\n   */\n  verticalScrollbarSize?: number;\n  /**\n   * Width (in px) of the vertical scrollbar slider.\n   * Defaults to `verticalScrollbarSize`\n   */\n  verticalSliderSize?: number;\n  /**\n   * Render arrows (top/bottom) for the vertical scrollbar.\n   * Defaults to false.\n   */\n  verticalHasArrows?: boolean;\n  /**\n   * Scroll gutter clicks move by page vs. jump to position.\n   * Defaults to false.\n   */\n  scrollByPage?: boolean;\n}\n\nexport interface IScrollableElementChangeOptions {\n  handleMouseWheel?: boolean;\n  mouseWheelScrollSensitivity?: number;\n  fastScrollSensitivity?: number;\n  scrollPredominantAxis?: boolean;\n  horizontal?: ScrollbarVisibility;\n  horizontalScrollbarSize?: number;\n  horizontalHasArrows?: boolean;\n  vertical?: ScrollbarVisibility;\n  verticalScrollbarSize?: number;\n  verticalHasArrows?: boolean;\n  scrollByPage?: boolean;\n}\n\nexport interface IScrollableElementResolvedOptions {\n  lazyRender: boolean;\n  className: string;\n  useShadows: boolean;\n  handleMouseWheel: boolean;\n  flipAxes: boolean;\n  scrollYToX: boolean;\n  consumeMouseWheelIfScrollbarIsNeeded: boolean;\n  alwaysConsumeMouseWheel: boolean;\n  mouseWheelScrollSensitivity: number;\n  fastScrollSensitivity: number;\n  scrollPredominantAxis: boolean;\n  mouseWheelSmoothScroll: boolean;\n  listenOnDomNode: HTMLElement | null;\n  horizontal: ScrollbarVisibility;\n  horizontalScrollbarSize: number;\n  horizontalSliderSize: number;\n  horizontalHasArrows: boolean;\n  vertical: ScrollbarVisibility;\n  verticalScrollbarSize: number;\n  verticalSliderSize: number;\n  verticalHasArrows: boolean;\n  scrollByPage: boolean;\n}\n"
  },
  {
    "path": "src/browser/scrollable/scrollbarArrow.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor';\nimport { Widget } from './widget';\nimport { TimeoutTimer } from 'common/Async';\nimport * as dom from '../Dom';\n\nexport interface IScrollbarArrowOptions {\n  handleActivate: () => void;\n  className: string;\n  // icon: ThemeIcon;\n\n  bgWidth: number;\n  bgHeight: number;\n\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\n\nexport class ScrollbarArrow extends Widget {\n\n  private _handleActivate: () => void;\n  public bgDomNode: HTMLElement;\n  public domNode: HTMLElement;\n  private _pointerdownRepeatTimer: dom.WindowIntervalTimer;\n  private _pointerdownScheduleRepeatTimer: TimeoutTimer;\n  private _pointerMoveMonitor: GlobalPointerMoveMonitor;\n\n  constructor(opts: IScrollbarArrowOptions) {\n    super();\n    this._handleActivate = opts.handleActivate;\n\n    this.bgDomNode = document.createElement('div');\n    this.bgDomNode.className = 'xterm-arrow-background';\n    this.bgDomNode.style.position = 'absolute';\n    this.bgDomNode.style.width = opts.bgWidth + 'px';\n    this.bgDomNode.style.height = opts.bgHeight + 'px';\n    if (typeof opts.top !== 'undefined') {\n      this.bgDomNode.style.top = '0px';\n    }\n    if (typeof opts.left !== 'undefined') {\n      this.bgDomNode.style.left = '0px';\n    }\n    if (typeof opts.bottom !== 'undefined') {\n      this.bgDomNode.style.bottom = '0px';\n    }\n    if (typeof opts.right !== 'undefined') {\n      this.bgDomNode.style.right = '0px';\n    }\n\n    this.domNode = document.createElement('div');\n    this.domNode.className = opts.className;\n    // this.domNode.classList.add(...ThemeIcon.asClassNameArray(opts.icon));\n\n    this.domNode.style.position = 'absolute';\n    const arrowSize = Math.min(opts.bgWidth, opts.bgHeight);\n    this.domNode.style.width = arrowSize + 'px';\n    this.domNode.style.height = arrowSize + 'px';\n    if (typeof opts.top !== 'undefined') {\n      this.domNode.style.top = opts.top + 'px';\n    }\n    if (typeof opts.left !== 'undefined') {\n      this.domNode.style.left = opts.left + 'px';\n    }\n    if (typeof opts.bottom !== 'undefined') {\n      this.domNode.style.bottom = opts.bottom + 'px';\n    }\n    if (typeof opts.right !== 'undefined') {\n      this.domNode.style.right = opts.right + 'px';\n    }\n\n    this._pointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());\n    this._register(dom.addStandardDisposableListener(this.bgDomNode, dom.eventType.POINTER_DOWN, (e) => this._arrowPointerDown(e)));\n    this._register(dom.addStandardDisposableListener(this.domNode, dom.eventType.POINTER_DOWN, (e) => this._arrowPointerDown(e)));\n\n    this._pointerdownRepeatTimer = this._register(new dom.WindowIntervalTimer());\n    this._pointerdownScheduleRepeatTimer = this._register(new TimeoutTimer());\n  }\n\n  private _arrowPointerDown(e: PointerEvent): void {\n    if (!e.target || !(e.target instanceof Element)) {\n      return;\n    }\n    const scheduleRepeater = (): void => {\n      this._pointerdownRepeatTimer.cancelAndSet(() => this._handleActivate(), 1000 / 24, dom.getWindow(e));\n    };\n\n    this._handleActivate();\n    this._pointerdownRepeatTimer.cancel();\n    this._pointerdownScheduleRepeatTimer.cancelAndSet(scheduleRepeater, 200);\n\n    this._pointerMoveMonitor.startMonitoring(\n      e.target,\n      e.pointerId,\n      e.buttons,\n      (pointerMoveData) => { /* Intentional empty */ },\n      () => {\n        this._pointerdownRepeatTimer.cancel();\n        this._pointerdownScheduleRepeatTimer.cancel();\n      }\n    );\n\n    e.preventDefault();\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/scrollbarState.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\n/**\n * The minimal size of the slider (such that it can still be clickable).\n * The slider is artificially enlarged to keep it usable.\n */\nconst MINIMUM_SLIDER_SIZE = 20;\n\ninterface IScrollbarStateComputedValues {\n  computedAvailableSize: number;\n  computedIsNeeded: boolean;\n  computedSliderSize: number;\n  computedSliderRatio: number;\n  computedSliderPosition: number;\n}\n\nexport class ScrollbarState {\n\n  /**\n   * For the vertical scrollbar: the width.\n   * For the horizontal scrollbar: the height.\n   */\n  private _scrollbarSize: number;\n\n  /**\n   * For the vertical scrollbar: the height of the pair horizontal scrollbar.\n   * For the horizontal scrollbar: the width of the pair vertical scrollbar.\n   */\n  private _oppositeScrollbarSize: number;\n\n  /**\n   * For the vertical scrollbar: the height of the scrollbar's arrows.\n   * For the horizontal scrollbar: the width of the scrollbar's arrows.\n   */\n  private _arrowSize: number;\n\n  // --- variables\n  /**\n   * For the vertical scrollbar: the viewport height.\n   * For the horizontal scrollbar: the viewport width.\n   */\n  private _visibleSize: number;\n\n  /**\n   * For the vertical scrollbar: the scroll height.\n   * For the horizontal scrollbar: the scroll width.\n   */\n  private _scrollSize: number;\n\n  /**\n   * For the vertical scrollbar: the scroll top.\n   * For the horizontal scrollbar: the scroll left.\n   */\n  private _scrollPosition: number;\n\n  // --- computed variables\n\n  /**\n   * `visibleSize` - `oppositeScrollbarSize`\n   */\n  private _computedAvailableSize: number;\n  /**\n   * (`scrollSize` > 0 && `scrollSize` > `visibleSize`)\n   */\n  private _computedIsNeeded: boolean;\n\n  private _computedSliderSize: number;\n  private _computedSliderRatio: number;\n  private _computedSliderPosition: number;\n\n  constructor(arrowSize: number, scrollbarSize: number, oppositeScrollbarSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) {\n    this._scrollbarSize = Math.round(scrollbarSize);\n    this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);\n    this._arrowSize = Math.round(arrowSize);\n\n    this._visibleSize = visibleSize;\n    this._scrollSize = scrollSize;\n    this._scrollPosition = scrollPosition;\n\n    this._computedAvailableSize = 0;\n    this._computedIsNeeded = false;\n    this._computedSliderSize = 0;\n    this._computedSliderRatio = 0;\n    this._computedSliderPosition = 0;\n\n    this._refreshComputedValues();\n  }\n\n  public clone(): ScrollbarState {\n    return new ScrollbarState(this._arrowSize, this._scrollbarSize, this._oppositeScrollbarSize, this._visibleSize, this._scrollSize, this._scrollPosition);\n  }\n\n  public setVisibleSize(visibleSize: number): boolean {\n    const iVisibleSize = Math.round(visibleSize);\n    if (this._visibleSize !== iVisibleSize) {\n      this._visibleSize = iVisibleSize;\n      this._refreshComputedValues();\n      return true;\n    }\n    return false;\n  }\n\n  public setScrollSize(scrollSize: number): boolean {\n    const iScrollSize = Math.round(scrollSize);\n    if (this._scrollSize !== iScrollSize) {\n      this._scrollSize = iScrollSize;\n      this._refreshComputedValues();\n      return true;\n    }\n    return false;\n  }\n\n  public setScrollPosition(scrollPosition: number): boolean {\n    const iScrollPosition = Math.round(scrollPosition);\n    if (this._scrollPosition !== iScrollPosition) {\n      this._scrollPosition = iScrollPosition;\n      this._refreshComputedValues();\n      return true;\n    }\n    return false;\n  }\n\n  public setScrollbarSize(scrollbarSize: number): void {\n    this._scrollbarSize = Math.round(scrollbarSize);\n  }\n\n  public setArrowSize(arrowSize: number): void {\n    const iArrowSize = Math.round(arrowSize);\n    if (this._arrowSize !== iArrowSize) {\n      this._arrowSize = iArrowSize;\n      this._refreshComputedValues();\n    }\n  }\n\n  public setOppositeScrollbarSize(oppositeScrollbarSize: number): void {\n    this._oppositeScrollbarSize = Math.round(oppositeScrollbarSize);\n  }\n\n  private static _computeValues(\n    oppositeScrollbarSize: number,\n    arrowSize: number,\n    visibleSize: number,\n    scrollSize: number,\n    scrollPosition: number\n  ): IScrollbarStateComputedValues {\n    const computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize);\n    const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize);\n    const computedIsNeeded = (scrollSize > 0 && scrollSize > visibleSize);\n\n    if (!computedIsNeeded) {\n      return {\n        computedAvailableSize: Math.round(computedAvailableSize),\n        computedIsNeeded: computedIsNeeded,\n        computedSliderSize: Math.round(computedRepresentableSize),\n        computedSliderRatio: 0,\n        computedSliderPosition: 0,\n      };\n    }\n\n    const computedSliderSize = Math.round(Math.max(MINIMUM_SLIDER_SIZE, Math.floor(visibleSize * computedRepresentableSize / scrollSize)));\n\n    const computedSliderRatio = (computedRepresentableSize - computedSliderSize) / (scrollSize - visibleSize);\n    const computedSliderPosition = (scrollPosition * computedSliderRatio);\n\n    return {\n      computedAvailableSize: Math.round(computedAvailableSize),\n      computedIsNeeded: computedIsNeeded,\n      computedSliderSize: Math.round(computedSliderSize),\n      computedSliderRatio: computedSliderRatio,\n      computedSliderPosition: Math.round(computedSliderPosition),\n    };\n  }\n\n  private _refreshComputedValues(): void {\n    const r = ScrollbarState._computeValues(this._oppositeScrollbarSize, this._arrowSize, this._visibleSize, this._scrollSize, this._scrollPosition);\n    this._computedAvailableSize = r.computedAvailableSize;\n    this._computedIsNeeded = r.computedIsNeeded;\n    this._computedSliderSize = r.computedSliderSize;\n    this._computedSliderRatio = r.computedSliderRatio;\n    this._computedSliderPosition = r.computedSliderPosition;\n  }\n\n  public getArrowSize(): number {\n    return this._arrowSize;\n  }\n\n  public getScrollPosition(): number {\n    return this._scrollPosition;\n  }\n\n  public getRectangleLargeSize(): number {\n    return this._computedAvailableSize;\n  }\n\n  public getRectangleSmallSize(): number {\n    return this._scrollbarSize;\n  }\n\n  public isNeeded(): boolean {\n    return this._computedIsNeeded;\n  }\n\n  public getSliderSize(): number {\n    return this._computedSliderSize;\n  }\n\n  public getSliderPosition(): number {\n    return this._computedSliderPosition;\n  }\n\n  public getDesiredScrollPositionFromOffset(offset: number): number {\n    if (!this._computedIsNeeded) {\n      return 0;\n    }\n\n    const desiredSliderPosition = offset - this._arrowSize - this._computedSliderSize / 2;\n    return Math.round(desiredSliderPosition / this._computedSliderRatio);\n  }\n\n  public getDesiredScrollPositionFromOffsetPaged(offset: number): number {\n    if (!this._computedIsNeeded) {\n      return 0;\n    }\n\n    const correctedOffset = offset - this._arrowSize;\n    let desiredScrollPosition = this._scrollPosition;\n    if (correctedOffset < this._computedSliderPosition) {\n      desiredScrollPosition -= this._visibleSize;\n    } else {\n      desiredScrollPosition += this._visibleSize;\n    }\n    return desiredScrollPosition;\n  }\n\n  public getDesiredScrollPositionFromDelta(delta: number): number {\n    if (!this._computedIsNeeded) {\n      return 0;\n    }\n\n    const desiredSliderPosition = this._computedSliderPosition + delta;\n    return Math.round(desiredSliderPosition / this._computedSliderRatio);\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/scrollbarVisibilityController.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { FastDomNode } from './fastDomNode';\nimport { TimeoutTimer } from 'common/Async';\nimport { Disposable } from 'common/Lifecycle';\nimport { ScrollbarVisibility } from './scrollable';\n\nexport class ScrollbarVisibilityController extends Disposable {\n  private _visibility: ScrollbarVisibility;\n  private _visibleClassName: string;\n  private _invisibleClassName: string;\n  private _domNode: FastDomNode<HTMLElement> | null;\n  private _rawShouldBeVisible: boolean;\n  private _shouldBeVisible: boolean;\n  private _isNeeded: boolean;\n  private _isVisible: boolean;\n  private _revealTimer: TimeoutTimer;\n\n  constructor(visibility: ScrollbarVisibility, visibleClassName: string, invisibleClassName: string) {\n    super();\n    this._visibility = visibility;\n    this._visibleClassName = visibleClassName;\n    this._invisibleClassName = invisibleClassName;\n    this._domNode = null;\n    this._isVisible = false;\n    this._isNeeded = false;\n    this._rawShouldBeVisible = false;\n    this._shouldBeVisible = false;\n    this._revealTimer = this._register(new TimeoutTimer());\n  }\n\n  public setVisibility(visibility: ScrollbarVisibility): void {\n    if (this._visibility !== visibility) {\n      this._visibility = visibility;\n      this._updateShouldBeVisible();\n    }\n  }\n\n  public setShouldBeVisible(rawShouldBeVisible: boolean): void {\n    this._rawShouldBeVisible = rawShouldBeVisible;\n    this._updateShouldBeVisible();\n  }\n\n  private _applyVisibilitySetting(): boolean {\n    if (this._visibility === ScrollbarVisibility.HIDDEN) {\n      return false;\n    }\n    if (this._visibility === ScrollbarVisibility.VISIBLE) {\n      return true;\n    }\n    return this._rawShouldBeVisible;\n  }\n\n  private _updateShouldBeVisible(): void {\n    const shouldBeVisible = this._applyVisibilitySetting();\n\n    if (this._shouldBeVisible !== shouldBeVisible) {\n      this._shouldBeVisible = shouldBeVisible;\n      this.ensureVisibility();\n    }\n  }\n\n  public setIsNeeded(isNeeded: boolean): void {\n    if (this._isNeeded !== isNeeded) {\n      this._isNeeded = isNeeded;\n      this.ensureVisibility();\n    }\n  }\n\n  public setDomNode(domNode: FastDomNode<HTMLElement>): void {\n    this._domNode = domNode;\n    this._domNode.setClassName(this._invisibleClassName);\n\n    this.setShouldBeVisible(false);\n  }\n\n  public ensureVisibility(): void {\n\n    if (!this._isNeeded) {\n      this._hide(false);\n      return;\n    }\n\n    if (this._shouldBeVisible) {\n      this._reveal();\n    } else {\n      this._hide(true);\n    }\n  }\n\n  private _reveal(): void {\n    if (this._isVisible) {\n      return;\n    }\n    this._isVisible = true;\n\n    this._revealTimer.setIfNotSet(() => {\n      this._domNode?.setClassName(this._visibleClassName);\n    }, 0);\n  }\n\n  private _hide(withFadeAway: boolean): void {\n    this._revealTimer.cancel();\n    if (!this._isVisible) {\n      return;\n    }\n    this._isVisible = false;\n    this._domNode?.setClassName(this._invisibleClassName + (withFadeAway ? ' xterm-fade' : ''));\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/touch.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as DomUtils from '../Dom';\nimport { Disposable, IDisposable, toDisposable } from 'common/Lifecycle';\n\nconst mainWindow = (typeof window === 'object' ? window : globalThis) as Window & typeof globalThis;\n\nfunction tail<T>(array: ArrayLike<T>, n: number = 0): T | undefined {\n  return array[array.length - (1 + n)];\n}\n\nfunction memoize(_target: any, key: string, descriptor: PropertyDescriptor): void {\n  let fnKey: string | null = null;\n  let fn: Function | null = null;\n\n  if (typeof descriptor.value === 'function') {\n    fnKey = 'value';\n    fn = descriptor.value;\n\n    if (fn!.length !== 0) {\n      console.warn('Memoize should only be used in functions with zero parameters');\n    }\n  } else if (typeof descriptor.get === 'function') {\n    fnKey = 'get';\n    fn = descriptor.get;\n  }\n\n  if (!fn || !fnKey) {\n    throw new Error('not supported');\n  }\n\n  const memoizeKey = `$memoize$${key}`;\n  const descriptorAny = descriptor as { [key: string]: any };\n  descriptorAny[fnKey] = function (...args: any[]) {\n    if (!this.hasOwnProperty(memoizeKey)) {\n      Object.defineProperty(this, memoizeKey, {\n        configurable: false,\n        enumerable: false,\n        writable: false,\n        value: fn.apply(this, args)\n      });\n    }\n\n    return (this as { [key: string]: any })[memoizeKey];\n  };\n}\n\nclass LinkedListNode<E> {\n\n  public static readonly Undefined = new LinkedListNode<any>(undefined);\n\n  public element: E;\n  public next: LinkedListNode<E>;\n  public prev: LinkedListNode<E>;\n\n  public constructor(element: E) {\n    this.element = element;\n    this.next = LinkedListNode.Undefined;\n    this.prev = LinkedListNode.Undefined;\n  }\n}\n\nclass LinkedList<E> {\n\n  private _first: LinkedListNode<E> = LinkedListNode.Undefined;\n  private _last: LinkedListNode<E> = LinkedListNode.Undefined;\n\n  public push(element: E): () => void {\n    return this._insert(element, true);\n  }\n\n  private _insert(element: E, atTheEnd: boolean): () => void {\n    const newNode = new LinkedListNode(element);\n    if (this._first === LinkedListNode.Undefined) {\n      this._first = newNode;\n      this._last = newNode;\n\n    } else if (atTheEnd) {\n      const oldLast = this._last;\n      this._last = newNode;\n      newNode.prev = oldLast;\n      oldLast.next = newNode;\n\n    } else {\n      const oldFirst = this._first;\n      this._first = newNode;\n      newNode.next = oldFirst;\n      oldFirst.prev = newNode;\n    }\n    let didRemove = false;\n    return () => {\n      if (!didRemove) {\n        didRemove = true;\n        this._remove(newNode);\n      }\n    };\n  }\n\n  private _remove(node: LinkedListNode<E>): void {\n    if (node.prev !== LinkedListNode.Undefined && node.next !== LinkedListNode.Undefined) {\n      const anchor = node.prev;\n      anchor.next = node.next;\n      node.next.prev = anchor;\n\n    } else if (node.prev === LinkedListNode.Undefined && node.next === LinkedListNode.Undefined) {\n      this._first = LinkedListNode.Undefined;\n      this._last = LinkedListNode.Undefined;\n\n    } else if (node.next === LinkedListNode.Undefined) {\n      this._last = this._last.prev!;\n      this._last.next = LinkedListNode.Undefined;\n\n    } else if (node.prev === LinkedListNode.Undefined) {\n      this._first = this._first.next!;\n      this._first.prev = LinkedListNode.Undefined;\n    }\n  }\n\n  public *[Symbol.iterator](): Iterator<E> {\n    let node = this._first;\n    while (node !== LinkedListNode.Undefined) {\n      yield node.element;\n      node = node.next;\n    }\n  }\n}\n\nexport namespace EventType {\n  export const TAP = '-xterm-gesturetap';\n  export const CHANGE = '-xterm-gesturechange';\n  export const START = '-xterm-gesturestart';\n  export const END = '-xterm-gesturesend';\n  export const CONTEXT_MENU = '-xterm-gesturecontextmenu';\n}\n\ninterface ITouchData {\n  id: number;\n  initialTarget: EventTarget;\n  initialTimeStamp: number;\n  initialPageX: number;\n  initialPageY: number;\n  rollingTimestamps: number[];\n  rollingPageX: number[];\n  rollingPageY: number[];\n}\n\nexport interface IGestureEvent extends MouseEvent {\n  initialTarget: EventTarget | undefined;\n  translationX: number;\n  translationY: number;\n  pageX: number;\n  pageY: number;\n  clientX: number;\n  clientY: number;\n  tapCount: number;\n}\n\ninterface ITouch {\n  identifier: number;\n  screenX: number;\n  screenY: number;\n  clientX: number;\n  clientY: number;\n  pageX: number;\n  pageY: number;\n  radiusX: number;\n  radiusY: number;\n  rotationAngle: number;\n  force: number;\n  target: Element;\n}\n\ninterface ITouchList {\n  [i: number]: ITouch;\n  length: number;\n  item(index: number): ITouch;\n  identifiedTouch(id: number): ITouch;\n}\n\ninterface ITouchEvent extends Event {\n  touches: ITouchList;\n  targetTouches: ITouchList;\n  changedTouches: ITouchList;\n}\n\nexport class Gesture extends Disposable {\n\n  private static readonly _scrollFriction = -0.005;\n  private static _instance: Gesture;\n  private static readonly _holdDelay = 700;\n\n  private _dispatched = false;\n  private readonly _targets = new LinkedList<HTMLElement>();\n  private readonly _ignoreTargets = new LinkedList<HTMLElement>();\n  private _handle: IDisposable | null;\n\n  private readonly _activeTouches: { [id: number]: ITouchData };\n\n  private _lastSetTapCountTime: number;\n\n  private static readonly _clearTapCountTime = 400; // ms\n\n\n  private constructor() {\n    super();\n\n    this._activeTouches = {};\n    this._handle = null;\n    this._lastSetTapCountTime = 0;\n\n    const targetWindow = mainWindow;\n    this._register(DomUtils.addDisposableListener(targetWindow.document, 'touchstart', (e: ITouchEvent) => this._handleTouchStart(e), { passive: false }));\n    this._register(DomUtils.addDisposableListener(targetWindow.document, 'touchend', (e: ITouchEvent) => this._handleTouchEnd(targetWindow, e)));\n    this._register(DomUtils.addDisposableListener(targetWindow.document, 'touchmove', (e: ITouchEvent) => this._handleTouchMove(e), { passive: false }));\n  }\n\n  public static addTarget(element: HTMLElement): IDisposable {\n    if (!Gesture.isTouchDevice()) {\n      return Disposable.None;\n    }\n    if (!Gesture._instance) {\n      Gesture._instance = new Gesture();\n    }\n\n    const remove = Gesture._instance._targets.push(element);\n    return toDisposable(remove);\n  }\n\n  public static ignoreTarget(element: HTMLElement): IDisposable {\n    if (!Gesture.isTouchDevice()) {\n      return Disposable.None;\n    }\n    if (!Gesture._instance) {\n      Gesture._instance = new Gesture();\n    }\n\n    const remove = Gesture._instance._ignoreTargets.push(element);\n    return toDisposable(remove);\n  }\n\n  @memoize\n  public static isTouchDevice(): boolean {\n    return 'ontouchstart' in mainWindow || navigator.maxTouchPoints > 0;\n  }\n\n  public override dispose(): void {\n    if (this._handle) {\n      this._handle.dispose();\n      this._handle = null;\n    }\n\n    super.dispose();\n  }\n\n  private _handleTouchStart(e: ITouchEvent): void {\n    const timestamp = Date.now();\n\n    if (this._handle) {\n      this._handle.dispose();\n      this._handle = null;\n    }\n\n    for (let i = 0, len = e.targetTouches.length; i < len; i++) {\n      const touch = e.targetTouches.item(i);\n\n      this._activeTouches[touch.identifier] = {\n        id: touch.identifier,\n        initialTarget: touch.target,\n        initialTimeStamp: timestamp,\n        initialPageX: touch.pageX,\n        initialPageY: touch.pageY,\n        rollingTimestamps: [timestamp],\n        rollingPageX: [touch.pageX],\n        rollingPageY: [touch.pageY]\n      };\n\n      const evt = this._newGestureEvent(EventType.START, touch.target);\n      evt.pageX = touch.pageX;\n      evt.pageY = touch.pageY;\n      this._dispatchEvent(evt);\n    }\n\n    if (this._dispatched) {\n      e.preventDefault();\n      e.stopPropagation();\n      this._dispatched = false;\n    }\n  }\n\n  private _handleTouchEnd(targetWindow: Window, e: ITouchEvent): void {\n    const timestamp = Date.now();\n\n    const activeTouchCount = Object.keys(this._activeTouches).length;\n\n    for (let i = 0, len = e.changedTouches.length; i < len; i++) {\n\n      const touch = e.changedTouches.item(i);\n\n      if (!this._activeTouches.hasOwnProperty(String(touch.identifier))) {\n        console.warn('move of an UNKNOWN touch', touch);\n        continue;\n      }\n\n      const data = this._activeTouches[touch.identifier];\n      const holdTime = Date.now() - data.initialTimeStamp;\n\n      if (holdTime < Gesture._holdDelay\n        && Math.abs(data.initialPageX - tail(data.rollingPageX)!) < 30\n        && Math.abs(data.initialPageY - tail(data.rollingPageY)!) < 30) {\n\n        const evt = this._newGestureEvent(EventType.TAP, data.initialTarget);\n        evt.pageX = tail(data.rollingPageX)!;\n        evt.pageY = tail(data.rollingPageY)!;\n        this._dispatchEvent(evt);\n\n      } else if (holdTime >= Gesture._holdDelay\n\t\t\t\t&& Math.abs(data.initialPageX - tail(data.rollingPageX)!) < 30\n\t\t\t\t&& Math.abs(data.initialPageY - tail(data.rollingPageY)!) < 30) {\n\n        const evt = this._newGestureEvent(EventType.CONTEXT_MENU, data.initialTarget);\n        evt.pageX = tail(data.rollingPageX)!;\n        evt.pageY = tail(data.rollingPageY)!;\n        this._dispatchEvent(evt);\n\n      } else if (activeTouchCount === 1) {\n        const finalX = tail(data.rollingPageX)!;\n        const finalY = tail(data.rollingPageY)!;\n\n        const deltaT = tail(data.rollingTimestamps)! - data.rollingTimestamps[0];\n        const deltaX = finalX - data.rollingPageX[0];\n        const deltaY = finalY - data.rollingPageY[0];\n\n        const dispatchTo = [...this._targets].filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget));\n        this._inertia(targetWindow, dispatchTo, timestamp,\n          Math.abs(deltaX) / deltaT,\n          deltaX > 0 ? 1 : -1,\n          finalX,\n          Math.abs(deltaY) / deltaT,\n          deltaY > 0 ? 1 : -1,\n          finalY\n        );\n      }\n\n\n      this._dispatchEvent(this._newGestureEvent(EventType.END, data.initialTarget));\n      delete this._activeTouches[touch.identifier];\n    }\n\n    if (this._dispatched) {\n      e.preventDefault();\n      e.stopPropagation();\n      this._dispatched = false;\n    }\n  }\n\n  private _newGestureEvent(type: string, initialTarget?: EventTarget): IGestureEvent {\n    const event = document.createEvent('CustomEvent') as unknown as IGestureEvent;\n    event.initEvent(type, false, true);\n    event.initialTarget = initialTarget;\n    event.tapCount = 0;\n    return event;\n  }\n\n  private _dispatchEvent(event: IGestureEvent): void {\n    if (event.type === EventType.TAP) {\n      const currentTime = (new Date()).getTime();\n      let setTapCount = 0;\n      if (currentTime - this._lastSetTapCountTime > Gesture._clearTapCountTime) {\n        setTapCount = 1;\n      } else {\n        setTapCount = 2;\n      }\n\n      this._lastSetTapCountTime = currentTime;\n      event.tapCount = setTapCount;\n    } else if (event.type === EventType.CHANGE || event.type === EventType.CONTEXT_MENU) {\n      this._lastSetTapCountTime = 0;\n    }\n\n    if (event.initialTarget instanceof Node) {\n      for (const ignoreTarget of this._ignoreTargets) {\n        if (ignoreTarget.contains(event.initialTarget)) {\n          return;\n        }\n      }\n\n      const targets: [number, HTMLElement][] = [];\n      for (const target of this._targets) {\n        if (target.contains(event.initialTarget)) {\n          let depth = 0;\n          let now: Node | null = event.initialTarget;\n          while (now && now !== target) {\n            depth++;\n            now = now.parentElement;\n          }\n          targets.push([depth, target]);\n        }\n      }\n\n      targets.sort((a, b) => a[0] - b[0]);\n\n      for (const [, target] of targets) {\n        target.dispatchEvent(event);\n        this._dispatched = true;\n      }\n    }\n  }\n\n  private _inertia(targetWindow: Window, dispatchTo: ReadonlyArray<EventTarget>, t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {\n    this._handle = DomUtils.scheduleAtNextAnimationFrame(targetWindow, () => {\n      const now = Date.now();\n\n      const deltaT = now - t1;\n      let deltaPosX = 0;\n      let deltaPosY = 0;\n      let stopped = true;\n\n      vX += Gesture._scrollFriction * deltaT;\n      vY += Gesture._scrollFriction * deltaT;\n\n      if (vX > 0) {\n        stopped = false;\n        deltaPosX = dirX * vX * deltaT;\n      }\n\n      if (vY > 0) {\n        stopped = false;\n        deltaPosY = dirY * vY * deltaT;\n      }\n\n      const evt = this._newGestureEvent(EventType.CHANGE);\n      evt.translationX = deltaPosX;\n      evt.translationY = deltaPosY;\n      dispatchTo.forEach(d => d.dispatchEvent(evt));\n\n      if (!stopped) {\n        this._inertia(targetWindow, dispatchTo, now, vX, dirX, x + deltaPosX, vY, dirY, y + deltaPosY);\n      }\n    });\n  }\n\n  private _handleTouchMove(e: ITouchEvent): void {\n    const timestamp = Date.now();\n\n    for (let i = 0, len = e.changedTouches.length; i < len; i++) {\n\n      const touch = e.changedTouches.item(i);\n\n      if (!this._activeTouches.hasOwnProperty(String(touch.identifier))) {\n        console.warn('end of an UNKNOWN touch', touch);\n        continue;\n      }\n\n      const data = this._activeTouches[touch.identifier];\n\n      const evt = this._newGestureEvent(EventType.CHANGE, data.initialTarget);\n      evt.translationX = touch.pageX - tail(data.rollingPageX)!;\n      evt.translationY = touch.pageY - tail(data.rollingPageY)!;\n      evt.pageX = touch.pageX;\n      evt.pageY = touch.pageY;\n      evt.clientX = touch.clientX;\n      evt.clientY = touch.clientY;\n      this._dispatchEvent(evt);\n\n      if (data.rollingPageX.length > 3) {\n        data.rollingPageX.shift();\n        data.rollingPageY.shift();\n        data.rollingTimestamps.shift();\n      }\n\n      data.rollingPageX.push(touch.pageX);\n      data.rollingPageY.push(touch.pageY);\n      data.rollingTimestamps.push(timestamp);\n    }\n\n    if (this._dispatched) {\n      e.preventDefault();\n      e.stopPropagation();\n      this._dispatched = false;\n    }\n  }\n}\n"
  },
  {
    "path": "src/browser/scrollable/verticalScrollbar.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { AbstractScrollbar, ISimplifiedPointerEvent, IScrollbarHost } from './abstractScrollbar';\nimport { IScrollableElementResolvedOptions } from './scrollableElementOptions';\nimport { ScrollbarState } from './scrollbarState';\nimport { INewScrollPosition, Scrollable, ScrollbarVisibility, IScrollEvent } from './scrollable';\nimport type { ScrollbarArrow } from './scrollbarArrow';\n\nexport class VerticalScrollbar extends AbstractScrollbar {\n  private _arrowUp: ScrollbarArrow | undefined;\n  private _arrowDown: ScrollbarArrow | undefined;\n  private _arrowScrollDelta: number = 0;\n\n  constructor(scrollable: Scrollable, options: IScrollableElementResolvedOptions, host: IScrollbarHost) {\n    const scrollDimensions = scrollable.getScrollDimensions();\n    const scrollPosition = scrollable.getCurrentScrollPosition();\n    const hasArrows = options.verticalHasArrows;\n    super({\n      lazyRender: options.lazyRender,\n      host: host,\n      scrollbarState: new ScrollbarState(\n        (hasArrows ? options.verticalScrollbarSize : 0),\n        (options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize),\n        0,\n        scrollDimensions.height,\n        scrollDimensions.scrollHeight,\n        scrollPosition.scrollTop\n      ),\n      visibility: options.vertical,\n      extraScrollbarClassName: 'xterm-vertical',\n      scrollable: scrollable,\n      scrollByPage: options.scrollByPage\n    });\n\n    this._setArrows(hasArrows, options.verticalScrollbarSize);\n\n    this._createSlider(0, Math.floor((options.verticalScrollbarSize - options.verticalSliderSize) / 2), options.verticalSliderSize, undefined);\n  }\n\n  protected _updateSlider(sliderSize: number, sliderPosition: number): void {\n    this.slider.setHeight(sliderSize);\n    this.slider.setTop(sliderPosition);\n  }\n\n  protected _renderDomNode(largeSize: number, smallSize: number): void {\n    this.domNode.setWidth(smallSize);\n    this.domNode.setHeight(largeSize);\n    this.domNode.setRight(0);\n    this.domNode.setTop(0);\n  }\n\n  public handleScroll(e: IScrollEvent): boolean {\n    this._shouldRender = this._handleElementScrollSize(e.scrollHeight) || this._shouldRender;\n    this._shouldRender = this._handleElementScrollPosition(e.scrollTop) || this._shouldRender;\n    this._shouldRender = this._handleElementSize(e.height) || this._shouldRender;\n    return this._shouldRender;\n  }\n\n  protected _pointerDownRelativePosition(offsetX: number, offsetY: number): number {\n    return offsetY;\n  }\n\n  protected _sliderPointerPosition(e: ISimplifiedPointerEvent): number {\n    return e.pageY;\n  }\n\n  protected _sliderOrthogonalPointerPosition(e: ISimplifiedPointerEvent): number {\n    return e.pageX;\n  }\n\n  protected _updateScrollbarSize(size: number): void {\n    this.slider.setWidth(size);\n  }\n\n  public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void {\n    target.scrollTop = scrollPosition;\n  }\n\n  private _arrowScroll(delta: number): void {\n    const currentPosition = this._scrollable.getCurrentScrollPosition();\n    this._scrollable.setScrollPositionNow({ scrollTop: currentPosition.scrollTop + delta });\n  }\n\n  private _setArrows(showArrows: boolean, size: number): void {\n    this._arrowScrollDelta = size;\n    if (!this._arrowUp || !this._arrowDown) {\n      const arrowDelta = 0;\n      this._arrowUp = this._createArrow({\n        className: 'xterm-scra xterm-arrow-up',\n        top: arrowDelta,\n        left: arrowDelta,\n        bgWidth: size,\n        bgHeight: size,\n        handleActivate: () => this._arrowScroll(-this._arrowScrollDelta)\n      });\n      this._arrowDown = this._createArrow({\n        className: 'xterm-scra xterm-arrow-down',\n        bottom: arrowDelta,\n        left: arrowDelta,\n        bgWidth: size,\n        bgHeight: size,\n        handleActivate: () => this._arrowScroll(this._arrowScrollDelta)\n      });\n    }\n\n    this._updateArrowSize(this._arrowUp, size);\n    this._updateArrowSize(this._arrowDown, size);\n\n    if (!this._arrowUp || !this._arrowDown) {\n      return;\n    }\n\n    const display = showArrows ? '' : 'none';\n    this._arrowUp.bgDomNode.style.display = display;\n    this._arrowUp.domNode.style.display = display;\n    this._arrowDown.bgDomNode.style.display = display;\n    this._arrowDown.domNode.style.display = display;\n  }\n\n  private _updateArrowSize(arrow: ScrollbarArrow | undefined, size: number): void {\n    if (!arrow) {\n      return;\n    }\n    arrow.bgDomNode.style.width = `${size}px`;\n    arrow.bgDomNode.style.height = `${size}px`;\n    arrow.domNode.style.width = `${size}px`;\n    arrow.domNode.style.height = `${size}px`;\n  }\n\n  public updateOptions(options: IScrollableElementResolvedOptions): void {\n    const arrowSize = options.verticalHasArrows ? options.verticalScrollbarSize : 0;\n    this._scrollbarState.setArrowSize(arrowSize);\n    this._setArrows(options.verticalHasArrows, options.verticalScrollbarSize);\n    this.updateScrollbarSize(options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize);\n    this._scrollbarState.setOppositeScrollbarSize(0);\n    this._visibilityController.setVisibility(options.vertical);\n    this._scrollByPage = options.scrollByPage;\n  }\n\n}\n"
  },
  {
    "path": "src/browser/scrollable/widget.ts",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as dom from '../Dom';\nimport { IMouseEvent, StandardMouseEvent } from './mouseEvent';\nimport { Disposable } from 'common/Lifecycle';\n\nexport abstract class Widget extends Disposable {\n\n  protected _onclick(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void {\n    this._register(dom.addDisposableListener(domNode, dom.eventType.CLICK, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e))));\n  }\n\n  protected _onmouseover(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void {\n    this._register(dom.addDisposableListener(domNode, dom.eventType.MOUSE_OVER, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e))));\n  }\n\n  protected _onmouseleave(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void {\n    this._register(dom.addDisposableListener(domNode, dom.eventType.MOUSE_LEAVE, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e))));\n  }\n}\n"
  },
  {
    "path": "src/browser/selection/SelectionModel.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { SelectionModel } from './SelectionModel';\nimport { MockBufferService } from 'common/TestUtils.test';\n\ndescribe('SelectionModel', () => {\n  let model: SelectionModel;\n\n  beforeEach(() => {\n    const bufferService = new MockBufferService(80, 2);\n    model = new SelectionModel(bufferService);\n  });\n\n  describe('clearSelection', () => {\n    it('should clear the final selection', () => {\n      model.selectionStart = [0, 0];\n      model.selectionEnd = [10, 2];\n      assert.deepEqual(model.finalSelectionStart, [0, 0]);\n      assert.deepEqual(model.finalSelectionEnd, [10, 2]);\n      model.clearSelection();\n      assert.deepEqual(model.finalSelectionStart, undefined);\n      assert.deepEqual(model.finalSelectionEnd, undefined);\n    });\n  });\n\n  describe('areSelectionValuesReversed', () => {\n    it('should return true when the selection end is before selection start', () => {\n      model.selectionStart = [1, 0];\n      model.selectionEnd = [0, 0];\n      assert.equal(model.areSelectionValuesReversed(), true);\n      model.selectionStart = [10, 2];\n      model.selectionEnd = [0, 0];\n      assert.equal(model.areSelectionValuesReversed(), true);\n    });\n    it('should return false when the selection end is after selection start', () => {\n      model.selectionStart = [0, 0];\n      model.selectionEnd = [1, 0];\n      assert.equal(model.areSelectionValuesReversed(), false);\n      model.selectionStart = [0, 0];\n      model.selectionEnd = [10, 2];\n      assert.equal(model.areSelectionValuesReversed(), false);\n    });\n  });\n\n  describe('onTrim', () => {\n    it('should trim a portion of the selection when a part of it is trimmed', () => {\n      model.selectionStart = [0, 0];\n      model.selectionEnd = [10, 2];\n      model.handleTrim(1);\n      assert.deepEqual(model.finalSelectionStart, [0, 0]);\n      assert.deepEqual(model.finalSelectionEnd, [10, 1]);\n      model.handleTrim(1);\n      assert.deepEqual(model.finalSelectionStart, [0, 0]);\n      assert.deepEqual(model.finalSelectionEnd, [10, 0]);\n    });\n    it('should clear selection when it is trimmed in its entirety', () => {\n      model.selectionStart = [0, 0];\n      model.selectionEnd = [10, 0];\n      model.handleTrim(1);\n      assert.deepEqual(model.finalSelectionStart, undefined);\n      assert.deepEqual(model.finalSelectionEnd, undefined);\n    });\n  });\n\n  describe('finalSelectionStart', () => {\n    it('should return the start of the buffer if select all is active', () => {\n      model.isSelectAllActive = true;\n      assert.deepEqual(model.finalSelectionStart, [0, 0]);\n    });\n    it('should return selection start if there is no selection end', () => {\n      model.selectionStart = [2, 2];\n      assert.deepEqual(model.finalSelectionStart, [2, 2]);\n    });\n    it('should return selection end if values are reversed', () => {\n      model.selectionStart = [2, 2];\n      model.selectionEnd = [3, 2];\n      assert.deepEqual(model.finalSelectionStart, [2, 2]);\n      model.selectionEnd = [1, 2];\n      assert.deepEqual(model.finalSelectionStart, [1, 2]);\n    });\n  });\n\n  describe('finalSelectionEnd', () => {\n    it('should return the end of the buffer if select all is active', () => {\n      model.isSelectAllActive = true;\n      assert.deepEqual(model.finalSelectionEnd, [80, 1]);\n    });\n    it('should return null if there is no selection start', () => {\n      assert.equal(model.finalSelectionEnd, undefined);\n      model.selectionEnd = [1, 2];\n      assert.equal(model.finalSelectionEnd, undefined);\n    });\n    it('should return selection start + length if there is no selection end', () => {\n      model.selectionStart = [2, 2];\n      model.selectionStartLength = 2;\n      assert.deepEqual(model.finalSelectionEnd, [4, 2]);\n    });\n    it('should return selection start + length if values are reversed', () => {\n      model.selectionStart = [2, 2];\n      model.selectionStartLength = 2;\n      model.selectionEnd = [2, 1];\n      assert.deepEqual(model.finalSelectionEnd, [4, 2]);\n    });\n    it('should return selection start + length if selection end is inside the start selection', () => {\n      model.selectionStart = [2, 2];\n      model.selectionStartLength = 2;\n      model.selectionEnd = [3, 2];\n      assert.deepEqual(model.finalSelectionEnd, [4, 2]);\n    });\n    it('should return the end on a different row when start + length overflows onto a following row', () => {\n      model.selectionStart = [78, 2];\n      model.selectionStartLength = 4;\n      assert.deepEqual(model.finalSelectionEnd, [2, 3]);\n    });\n    it('should return the end on a different row when start + length overflows onto a following row with selectionEnd inbetween', () => {\n      model.selectionStart = [78, 2];\n      model.selectionEnd = [79, 2];\n      model.selectionStartLength = 4;\n      assert.deepEqual(model.finalSelectionEnd, [2, 3]);\n    });\n    it('should return selection end if selection end is after selection start + length', () => {\n      model.selectionStart = [2, 2];\n      model.selectionStartLength = 2;\n      model.selectionEnd = [5, 2];\n      assert.deepEqual(model.finalSelectionEnd, [5, 2]);\n    });\n    it('should not include a trailing EOL when the selection ends at the end of a line', () => {\n      model.selectionStart = [0, 0];\n      model.selectionStartLength = 80;\n      assert.deepEqual(model.finalSelectionEnd, [80, 0]);\n      model.selectionStart = [0, 0];\n      model.selectionStartLength = 160;\n      assert.deepEqual(model.finalSelectionEnd, [80, 1]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/browser/selection/SelectionModel.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferService } from 'common/services/Services';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n  /**\n   * Whether select all is currently active.\n   */\n  public isSelectAllActive: boolean = false;\n\n  /**\n   * The minimal length of the selection from the start position. When double\n   * clicking on a word, the word will be selected which makes the selection\n   * start at the start of the word and makes this variable the length.\n   */\n  public selectionStartLength: number = 0;\n\n  /**\n   * The [x, y] position the selection starts at.\n   */\n  public selectionStart: [number, number] | undefined;\n\n  /**\n   * The [x, y] position the selection ends at.\n   */\n  public selectionEnd: [number, number] | undefined;\n\n  constructor(\n    private _bufferService: IBufferService\n  ) {\n  }\n\n  /**\n   * Clears the current selection.\n   */\n  public clearSelection(): void {\n    this.selectionStart = undefined;\n    this.selectionEnd = undefined;\n    this.isSelectAllActive = false;\n    this.selectionStartLength = 0;\n  }\n\n  /**\n   * The final selection start, taking into consideration select all.\n   */\n  public get finalSelectionStart(): [number, number] | undefined {\n    if (this.isSelectAllActive) {\n      return [0, 0];\n    }\n\n    if (!this.selectionEnd || !this.selectionStart) {\n      return this.selectionStart;\n    }\n\n    return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n  }\n\n  /**\n   * The final selection end, taking into consideration select all, double click\n   * word selection and triple click line selection.\n   */\n  public get finalSelectionEnd(): [number, number] | undefined {\n    if (this.isSelectAllActive) {\n      return [this._bufferService.cols, this._bufferService.buffer.ybase + this._bufferService.rows - 1];\n    }\n\n    if (!this.selectionStart) {\n      return undefined;\n    }\n\n    // Use the selection start + length if the end doesn't exist or they're reversed\n    if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n      const startPlusLength = this.selectionStart[0] + this.selectionStartLength;\n      if (startPlusLength > this._bufferService.cols) {\n        // Ensure the trailing EOL isn't included when the selection ends on the right edge\n        if (startPlusLength % this._bufferService.cols === 0) {\n          return [this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols) - 1];\n        }\n        return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];\n      }\n      return [startPlusLength, this.selectionStart[1]];\n    }\n\n    // Ensure the the word/line is selected after a double/triple click\n    if (this.selectionStartLength) {\n      // Select the larger of the two when start and end are on the same line\n      if (this.selectionEnd[1] === this.selectionStart[1]) {\n        // Keep the whole wrapped word/line selected if the content wraps multiple lines\n        const startPlusLength = this.selectionStart[0] + this.selectionStartLength;\n        if (startPlusLength > this._bufferService.cols) {\n          return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];\n        }\n        return [Math.max(startPlusLength, this.selectionEnd[0]), this.selectionEnd[1]];\n      }\n    }\n    return this.selectionEnd;\n  }\n\n  /**\n   * Returns whether the selection start and end are reversed.\n   */\n  public areSelectionValuesReversed(): boolean {\n    const start = this.selectionStart;\n    const end = this.selectionEnd;\n    if (!start || !end) {\n      return false;\n    }\n    return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n  }\n\n  /**\n   * Handle the buffer being trimmed, adjust the selection position.\n   * @param amount The amount the buffer is being trimmed.\n   * @returns Whether a refresh is necessary.\n   */\n  public handleTrim(amount: number): boolean {\n    // Adjust the selection position based on the trimmed amount.\n    if (this.selectionStart) {\n      this.selectionStart[1] -= amount;\n    }\n    if (this.selectionEnd) {\n      this.selectionEnd[1] -= amount;\n    }\n\n    // The selection has moved off the buffer, clear it.\n    if (this.selectionEnd && this.selectionEnd[1] < 0) {\n      this.clearSelection();\n      return true;\n    }\n\n    // If the selection start is trimmed, ensure the start column is 0.\n    if (this.selectionStart && this.selectionStart[1] < 0) {\n      this.selectionStart[1] = 0;\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/browser/selection/Types.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport interface ISelectionRedrawRequestEvent {\n  start: [number, number] | undefined;\n  end: [number, number] | undefined;\n  columnSelectMode: boolean;\n}\n\nexport interface ISelectionRequestScrollLinesEvent {\n  amount: number;\n  suppressScrollEvent: boolean;\n}\n"
  },
  {
    "path": "src/browser/services/CharSizeService.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IOptionsService } from 'common/services/Services';\nimport { ICharSizeService } from 'browser/services/Services';\nimport { Disposable } from 'common/Lifecycle';\nimport { Emitter } from 'common/Event';\n\nexport class CharSizeService extends Disposable implements ICharSizeService {\n  public serviceBrand: undefined;\n\n  public width: number = 0;\n  public height: number = 0;\n  private _measureStrategy: IMeasureStrategy;\n\n  public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }\n\n  private readonly _onCharSizeChange = this._register(new Emitter<void>());\n  public readonly onCharSizeChange = this._onCharSizeChange.event;\n\n  constructor(\n    document: Document,\n    parentElement: HTMLElement,\n    @IOptionsService private readonly _optionsService: IOptionsService\n  ) {\n    super();\n    try {\n      this._measureStrategy = this._register(new TextMetricsMeasureStrategy(this._optionsService));\n    } catch {\n      this._measureStrategy = this._register(new DomMeasureStrategy(document, parentElement, this._optionsService));\n    }\n    this._register(this._optionsService.onMultipleOptionChange(['fontFamily', 'fontSize'], () => this.measure()));\n  }\n\n  public measure(): void {\n    const result = this._measureStrategy.measure();\n    if (result.width !== this.width || result.height !== this.height) {\n      this.width = result.width;\n      this.height = result.height;\n      this._onCharSizeChange.fire();\n    }\n  }\n}\n\ninterface IMeasureStrategy {\n  measure(): Readonly<IMeasureResult>;\n}\n\ninterface IMeasureResult {\n  width: number;\n  height: number;\n}\n\nconst enum DomMeasureStrategyConstants {\n  REPEAT = 32\n}\n\nabstract class BaseMeasureStategy extends Disposable implements IMeasureStrategy {\n  protected _result: IMeasureResult = { width: 0, height: 0 };\n\n  protected _validateAndSet(width: number | undefined, height: number | undefined): void {\n    // If values are 0 then the element is likely currently display:none, in which case we should\n    // retain the previous value.\n    if (width !== undefined && width > 0 && height !== undefined && height > 0) {\n      this._result.width = width;\n      this._result.height = height;\n    }\n  }\n\n  public abstract measure(): Readonly<IMeasureResult>;\n}\n\nclass DomMeasureStrategy extends BaseMeasureStategy {\n  private _measureElement: HTMLElement;\n\n  constructor(\n    private _document: Document,\n    private _parentElement: HTMLElement,\n    private _optionsService: IOptionsService\n  ) {\n    super();\n    this._measureElement = this._document.createElement('span');\n    this._measureElement.classList.add('xterm-char-measure-element');\n    this._measureElement.textContent = 'W'.repeat(DomMeasureStrategyConstants.REPEAT);\n    this._measureElement.setAttribute('aria-hidden', 'true');\n    this._measureElement.style.whiteSpace = 'pre';\n    this._measureElement.style.fontKerning = 'none';\n    this._parentElement.appendChild(this._measureElement);\n  }\n\n  public measure(): Readonly<IMeasureResult> {\n    this._measureElement.style.fontFamily = this._optionsService.rawOptions.fontFamily;\n    this._measureElement.style.fontSize = `${this._optionsService.rawOptions.fontSize}px`;\n\n    // Note that this triggers a synchronous layout\n    this._validateAndSet(Number(this._measureElement.offsetWidth) / DomMeasureStrategyConstants.REPEAT, Number(this._measureElement.offsetHeight));\n\n    return this._result;\n  }\n}\n\nclass TextMetricsMeasureStrategy extends BaseMeasureStategy {\n  private _canvas: OffscreenCanvas;\n  private _ctx: OffscreenCanvasRenderingContext2D;\n\n  constructor(\n    private _optionsService: IOptionsService\n  ) {\n    super();\n    // This will throw if any required API is not supported\n    this._canvas = new OffscreenCanvas(100, 100);\n    this._ctx = this._canvas.getContext('2d')!;\n    const a = this._ctx.measureText('W');\n    if (!('width' in a && 'fontBoundingBoxAscent' in a && 'fontBoundingBoxDescent' in a)) {\n      throw new Error('Required font metrics not supported');\n    }\n  }\n\n  public measure(): Readonly<IMeasureResult> {\n    this._ctx.font = `${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;\n    const metrics = this._ctx.measureText('W');\n    this._validateAndSet(metrics.width, metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent);\n    return this._result;\n  }\n}\n"
  },
  {
    "path": "src/browser/services/CharacterJoinerService.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { ICharacterJoinerService } from 'browser/services/Services';\nimport { CharacterJoinerService } from 'browser/services/CharacterJoinerService';\nimport { BufferLine } from 'common/buffer/BufferLine';\nimport { IBufferLine } from 'common/Types';\nimport { CellData } from 'common/buffer/CellData';\nimport { MockBufferService, createCellData } from 'common/TestUtils.test';\n\ndescribe('CharacterJoinerService', () => {\n  let service: ICharacterJoinerService;\n\n  beforeEach(() => {\n    const bufferService = new MockBufferService(16, 10);\n    const lines = bufferService.buffer.lines;\n    lines.set(0, lineData([['a -> b -> c -> d']]));\n    lines.set(1, lineData([['a -> b => c -> d']]));\n    lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]]));\n\n    lines.set(3, lineData([['no joined ranges']]));\n    lines.set(4, new BufferLine(0));\n    lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));\n    const line6 = lineData([['wi']]);\n    line6.resize(line6.length + 1, createCellData(0, '￥', 2));\n    line6.resize(line6.length + 1, createCellData(0, '', 0));\n    let sub = lineData([['deemo']]);\n    let oldSize = line6.length;\n    line6.resize(oldSize + sub.length, createCellData(0, '', 0));\n    for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData()));\n    line6.resize(line6.length + 1, CellData.fromCharData([0, '\\xf0\\x9f\\x98\\x81', 1, 128513]));\n    line6.resize(line6.length + 1, createCellData(0, ' ', 1));\n    sub = lineData([['jiabc']]);\n    oldSize = line6.length;\n    line6.resize(oldSize + sub.length, createCellData(0, '', 0));\n    for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData()));\n    lines.set(6, line6);\n\n    service = new CharacterJoinerService(bufferService);\n  });\n\n  it('has no joiners upon creation', () => {\n    assert.deepEqual(service.getJoinedCharacters(0), []);\n  });\n\n  it('returns ranges matched by the registered joiners', () => {\n    service.register(substringJoiner('->'));\n    assert.deepEqual(\n      service.getJoinedCharacters(0),\n      [[2, 4], [7, 9], [12, 14]]\n    );\n  });\n\n  it('processes the input using all provided joiners', () => {\n    service.register(substringJoiner('->'));\n    assert.deepEqual(\n      service.getJoinedCharacters(1),\n      [[2, 4], [12, 14]]\n    );\n\n    service.register(substringJoiner('=>'));\n    assert.deepEqual(\n      service.getJoinedCharacters(1),\n      [[2, 4], [7, 9], [12, 14]]\n    );\n  });\n\n  it('removes deregistered joiners from future calls', () => {\n    const joiner1 = service.register(substringJoiner('->'));\n    const joiner2 = service.register(substringJoiner('=>'));\n    assert.deepEqual(\n      service.getJoinedCharacters(1),\n      [[2, 4], [7, 9], [12, 14]]\n    );\n\n    service.deregister(joiner1);\n    assert.deepEqual(\n      service.getJoinedCharacters(1),\n      [[7, 9]]\n    );\n\n    service.deregister(joiner2);\n    assert.deepEqual(\n      service.getJoinedCharacters(1),\n      []\n    );\n  });\n\n  it('doesn\\'t process joins on differently-styled characters', () => {\n    service.register(substringJoiner('->'));\n    assert.deepEqual(\n      service.getJoinedCharacters(2),\n      [[2, 4], [12, 14]]\n    );\n  });\n\n  it('returns an empty list of ranges if there is nothing to be joined', () => {\n    service.register(substringJoiner('->'));\n    assert.deepEqual(\n      service.getJoinedCharacters(3),\n      []\n    );\n  });\n\n  it('returns an empty list of ranges if the line is empty', () => {\n    service.register(substringJoiner('->'));\n    assert.deepEqual(\n      service.getJoinedCharacters(4),\n      []\n    );\n  });\n\n  it('returns false when trying to deregister a joiner that does not exist', () => {\n    service.register(substringJoiner('->'));\n    assert.deepEqual(service.deregister(123), false);\n    assert.deepEqual(\n      service.getJoinedCharacters(0),\n      [[2, 4], [7, 9], [12, 14]]\n    );\n  });\n\n  it('doesn\\'t process same-styled ranges that only have one character', () => {\n    service.register(substringJoiner('a'));\n    service.register(substringJoiner('b'));\n    service.register(substringJoiner('d'));\n    assert.deepEqual(\n      service.getJoinedCharacters(5),\n      [[5, 6]]\n    );\n  });\n\n  it('handles ranges that extend all the way to the end of the line', () => {\n    service.register(substringJoiner('-> d'));\n    assert.deepEqual(\n      service.getJoinedCharacters(2),\n      [[12, 16]]\n    );\n  });\n\n  it('handles adjacent ranges', () => {\n    service.register(substringJoiner('->'));\n    service.register(substringJoiner('> c '));\n    assert.deepEqual(\n      service.getJoinedCharacters(2),\n      [[2, 4], [8, 12], [12, 14]]\n    );\n  });\n\n  it('handles fullwidth characters in the middle of ranges', () => {\n    service.register(substringJoiner('wi￥de'));\n    assert.deepEqual(\n      service.getJoinedCharacters(6),\n      [[0, 6]]\n    );\n  });\n\n  it('handles fullwidth characters at the end of ranges', () => {\n    service.register(substringJoiner('wi￥'));\n    assert.deepEqual(\n      service.getJoinedCharacters(6),\n      [[0, 4]]\n    );\n  });\n\n  it('handles emojis in the middle of ranges', () => {\n    service.register(substringJoiner('emo\\xf0\\x9f\\x98\\x81 ji'));\n    assert.deepEqual(\n      service.getJoinedCharacters(6),\n      [[6, 13]]\n    );\n  });\n\n  it('handles emojis at the end of ranges', () => {\n    service.register(substringJoiner('emo\\xf0\\x9f\\x98\\x81 '));\n    assert.deepEqual(\n      service.getJoinedCharacters(6),\n      [[6, 11]]\n    );\n  });\n\n  it('handles ranges after wide and emoji characters', () => {\n    service.register(substringJoiner('abc'));\n    assert.deepEqual(\n      service.getJoinedCharacters(6),\n      [[13, 16]]\n    );\n  });\n\n  describe('range merging', () => {\n    it('inserts a new range before the existing ones', () => {\n      service.register(() => [[1, 2], [2, 3]]);\n      service.register(() => [[0, 1]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 1], [1, 2], [2, 3]]\n      );\n    });\n\n    it('inserts in between two ranges', () => {\n      service.register(() => [[0, 2], [4, 6]]);\n      service.register(() => [[2, 4]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 2], [2, 4], [4, 6]]\n      );\n    });\n\n    it('inserts after the last range', () => {\n      service.register(() => [[0, 2], [4, 6]]);\n      service.register(() => [[6, 8]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 2], [4, 6], [6, 8]]\n      );\n    });\n\n    it('extends the beginning of a range', () => {\n      service.register(() => [[0, 2], [4, 6]]);\n      service.register(() => [[3, 5]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 2], [3, 6]]\n      );\n    });\n\n    it('extends the end of a range', () => {\n      service.register(() => [[0, 2], [4, 6]]);\n      service.register(() => [[1, 4]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 4], [4, 6]]\n      );\n    });\n\n    it('extends the last range', () => {\n      service.register(() => [[0, 2], [4, 6]]);\n      service.register(() => [[5, 7]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 2], [4, 7]]\n      );\n    });\n\n    it('connects two ranges', () => {\n      service.register(() => [[0, 2], [4, 6]]);\n      service.register(() => [[1, 5]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 6]]\n      );\n    });\n\n    it('connects more than two ranges', () => {\n      service.register(() => [[0, 2], [4, 6], [8, 10], [12, 14]]);\n      service.register(() => [[1, 10]]);\n      assert.deepEqual(\n        service.getJoinedCharacters(0),\n        [[0, 10], [12, 14]]\n      );\n    });\n  });\n});\n\ntype IPartialLineData = ([string] | [string, number]);\n\nfunction lineData(data: IPartialLineData[]): IBufferLine {\n  const tline = new BufferLine(0);\n  for (let i = 0; i < data.length; ++i) {\n    const line = data[i][0];\n    const attr = (data[i][1] || 0) as number;\n    const offset = tline.length;\n    tline.resize(tline.length + line.split('').length, createCellData(0, '', 0));\n    line.split('').map((char, idx) => tline.setCell(idx + offset, createCellData(attr, char, 1)));\n  }\n  return tline;\n}\n\nfunction substringJoiner(substring: string): (sequence: string) => [number, number][] {\n  return (sequence: string): [number, number][] => {\n    const ranges: [number, number][] = [];\n    let searchIndex = 0;\n    let matchIndex = -1;\n\n    while ((matchIndex = sequence.indexOf(substring, searchIndex)) !== -1) {\n      const matchEndIndex = matchIndex + substring.length;\n      searchIndex = matchEndIndex;\n      ranges.push([matchIndex, matchEndIndex]);\n    }\n\n    return ranges;\n  };\n}\n"
  },
  {
    "path": "src/browser/services/CharacterJoinerService.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferLine, ICellData, CharData } from 'common/Types';\nimport { ICharacterJoiner } from 'browser/Types';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';\nimport { CellData } from 'common/buffer/CellData';\nimport { IBufferService } from 'common/services/Services';\nimport { ICharacterJoinerService } from 'browser/services/Services';\n\nexport class JoinedCellData extends AttributeData implements ICellData {\n  private _width: number;\n  // .content carries no meaning for joined CellData, simply nullify it\n  // thus we have to overload all other .content accessors\n  public content: number = 0;\n  public fg: number;\n  public bg: number;\n  public combinedData: string = '';\n\n  constructor(firstCell: ICellData, chars: string, width: number) {\n    super();\n    this.fg = firstCell.fg;\n    this.bg = firstCell.bg;\n    this.combinedData = chars;\n    this._width = width;\n  }\n\n  public isCombined(): number {\n    // always mark joined cell data as combined\n    return Content.IS_COMBINED_MASK;\n  }\n\n  public getWidth(): number {\n    return this._width;\n  }\n\n  public getChars(): string {\n    return this.combinedData;\n  }\n\n  public getCode(): number {\n    // code always gets the highest possible fake codepoint (read as -1)\n    // this is needed as code is used by caches as identifier\n    return 0x1FFFFF;\n  }\n\n  public setFromCharData(value: CharData): void {\n    throw new Error('not implemented');\n  }\n\n  public getAsCharData(): CharData {\n    return [this.fg, this.getChars(), this.getWidth(), this.getCode()];\n  }\n}\n\nexport class CharacterJoinerService implements ICharacterJoinerService {\n  public serviceBrand: undefined;\n\n  private _characterJoiners: ICharacterJoiner[] = [];\n  private _nextCharacterJoinerId: number = 0;\n  private _workCell: CellData = new CellData();\n\n  constructor(\n    @IBufferService private _bufferService: IBufferService\n  ) { }\n\n  public register(handler: (text: string) => [number, number][]): number {\n    const joiner: ICharacterJoiner = {\n      id: this._nextCharacterJoinerId++,\n      handler\n    };\n\n    this._characterJoiners.push(joiner);\n    return joiner.id;\n  }\n\n  public deregister(joinerId: number): boolean {\n    for (let i = 0; i < this._characterJoiners.length; i++) {\n      if (this._characterJoiners[i].id === joinerId) {\n        this._characterJoiners.splice(i, 1);\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  public getJoinedCharacters(row: number): [number, number][] {\n    if (this._characterJoiners.length === 0) {\n      return [];\n    }\n\n    const line = this._bufferService.buffer.lines.get(row);\n    if (!line || line.length === 0) {\n      return [];\n    }\n\n    const ranges: [number, number][] = [];\n    const lineStr = line.translateToString(true);\n\n    // Because some cells can be represented by multiple javascript characters,\n    // we track the cell and the string indexes separately. This allows us to\n    // translate the string ranges we get from the joiners back into cell ranges\n    // for use when rendering\n    let rangeStartColumn = 0;\n    let currentStringIndex = 0;\n    let rangeStartStringIndex = 0;\n    let rangeAttrFG = line.getFg(0);\n    let rangeAttrBG = line.getBg(0);\n\n    for (let x = 0; x < line.getTrimmedLength(); x++) {\n      line.loadCell(x, this._workCell);\n\n      if (this._workCell.getWidth() === 0) {\n        // If this character is of width 0, skip it.\n        continue;\n      }\n\n      // End of range\n      if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) {\n        // If we ended up with a sequence of more than one character,\n        // look for ranges to join.\n        if (x - rangeStartColumn > 1) {\n          const joinedRanges = this._getJoinedRanges(\n            lineStr,\n            rangeStartStringIndex,\n            currentStringIndex,\n            line,\n            rangeStartColumn\n          );\n          for (let i = 0; i < joinedRanges.length; i++) {\n            ranges.push(joinedRanges[i]);\n          }\n        }\n\n        // Reset our markers for a new range.\n        rangeStartColumn = x;\n        rangeStartStringIndex = currentStringIndex;\n        rangeAttrFG = this._workCell.fg;\n        rangeAttrBG = this._workCell.bg;\n      }\n\n      currentStringIndex += this._workCell.getChars().length || WHITESPACE_CELL_CHAR.length;\n    }\n\n    // Process any trailing ranges.\n    if (this._bufferService.cols - rangeStartColumn > 1) {\n      const joinedRanges = this._getJoinedRanges(\n        lineStr,\n        rangeStartStringIndex,\n        currentStringIndex,\n        line,\n        rangeStartColumn\n      );\n      for (let i = 0; i < joinedRanges.length; i++) {\n        ranges.push(joinedRanges[i]);\n      }\n    }\n\n    return ranges;\n  }\n\n  /**\n   * Given a segment of a line of text, find all ranges of text that should be\n   * joined in a single rendering unit. Ranges are internally converted to\n   * column ranges, rather than string ranges.\n   * @param line String representation of the full line of text\n   * @param startIndex Start position of the range to search in the string (inclusive)\n   * @param endIndex End position of the range to search in the string (exclusive)\n   */\n  private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, startCol: number): [number, number][] {\n    const text = line.substring(startIndex, endIndex);\n    // At this point we already know that there is at least one joiner so\n    // we can just pull its value and assign it directly rather than\n    // merging it into an empty array, which incurs unnecessary writes.\n    let allJoinedRanges: [number, number][] = [];\n    try {\n      allJoinedRanges = this._characterJoiners[0].handler(text);\n    } catch (error) {\n      console.error(error);\n    }\n    for (let i = 1; i < this._characterJoiners.length; i++) {\n      // We merge any overlapping ranges across the different joiners\n      try {\n        const joinerRanges = this._characterJoiners[i].handler(text);\n        for (let j = 0; j < joinerRanges.length; j++) {\n          CharacterJoinerService._mergeRanges(allJoinedRanges, joinerRanges[j]);\n        }\n      } catch (error) {\n        console.error(error);\n      }\n    }\n    this._stringRangesToCellRanges(allJoinedRanges, lineData, startCol);\n    return allJoinedRanges;\n  }\n\n  /**\n   * Modifies the provided ranges in-place to adjust for variations between\n   * string length and cell width so that the range represents a cell range,\n   * rather than the string range the joiner provides.\n   * @param ranges String ranges containing start (inclusive) and end (exclusive) index\n   * @param line Cell data for the relevant line in the terminal\n   * @param startCol Offset within the line to start from\n   */\n  private _stringRangesToCellRanges(ranges: [number, number][], line: IBufferLine, startCol: number): void {\n    let currentRangeIndex = 0;\n    let currentRangeStarted = false;\n    let currentStringIndex = 0;\n    let currentRange = ranges[currentRangeIndex];\n\n    // If we got through all of the ranges, stop searching\n    if (!currentRange) {\n      return;\n    }\n\n    for (let x = startCol; x < this._bufferService.cols; x++) {\n      const width = line.getWidth(x);\n      const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;\n\n      // We skip zero-width characters when creating the string to join the text\n      // so we do the same here\n      if (width === 0) {\n        continue;\n      }\n\n      // Adjust the start of the range\n      if (!currentRangeStarted && currentRange[0] <= currentStringIndex) {\n        currentRange[0] = x;\n        currentRangeStarted = true;\n      }\n\n      // Adjust the end of the range\n      if (currentRange[1] <= currentStringIndex) {\n        currentRange[1] = x;\n\n        // We're finished with this range, so we move to the next one\n        currentRange = ranges[++currentRangeIndex];\n\n        // If there are no more ranges left, stop searching\n        if (!currentRange) {\n          break;\n        }\n\n        // Ranges can be on adjacent characters. Because the end index of the\n        // ranges are exclusive, this means that the index for the start of a\n        // range can be the same as the end index of the previous range. To\n        // account for the start of the next range, we check here just in case.\n        if (currentRange[0] <= currentStringIndex) {\n          currentRange[0] = x;\n          currentRangeStarted = true;\n        } else {\n          currentRangeStarted = false;\n        }\n      }\n\n      // Adjust the string index based on the character length to line up with\n      // the column adjustment\n      currentStringIndex += length;\n    }\n\n    // If there is still a range left at the end, it must extend all the way to\n    // the end of the line.\n    if (currentRange) {\n      currentRange[1] = this._bufferService.cols;\n    }\n  }\n\n  /**\n   * Merges the range defined by the provided start and end into the list of\n   * existing ranges. The merge is done in place on the existing range for\n   * performance and is also returned.\n   * @param ranges Existing range list\n   * @param newRange Tuple of two numbers representing the new range to merge in.\n   * @returns The ranges input with the new range merged in place\n   */\n  private static _mergeRanges(ranges: [number, number][], newRange: [number, number]): [number, number][] {\n    let inRange = false;\n    for (let i = 0; i < ranges.length; i++) {\n      const range = ranges[i];\n      if (!inRange) {\n        if (newRange[1] <= range[0]) {\n          // Case 1: New range is before the search range\n          ranges.splice(i, 0, newRange);\n          return ranges;\n        }\n\n        if (newRange[1] <= range[1]) {\n          // Case 2: New range is either wholly contained within the\n          // search range or overlaps with the front of it\n          range[0] = Math.min(newRange[0], range[0]);\n          return ranges;\n        }\n\n        if (newRange[0] < range[1]) {\n          // Case 3: New range either wholly contains the search range\n          // or overlaps with the end of it\n          range[0] = Math.min(newRange[0], range[0]);\n          inRange = true;\n        }\n\n        // Case 4: New range starts after the search range\n        continue;\n      } else {\n        if (newRange[1] <= range[0]) {\n          // Case 5: New range extends from previous range but doesn't\n          // reach the current one\n          ranges[i - 1][1] = newRange[1];\n          return ranges;\n        }\n\n        if (newRange[1] <= range[1]) {\n          // Case 6: New range extends from prvious range into the\n          // current range\n          ranges[i - 1][1] = Math.max(newRange[1], range[1]);\n          ranges.splice(i, 1);\n          return ranges;\n        }\n\n        // Case 7: New range extends from previous range past the\n        // end of the current range\n        ranges.splice(i, 1);\n        i--;\n      }\n    }\n\n    if (inRange) {\n      // Case 8: New range extends past the last existing range\n      ranges[ranges.length - 1][1] = newRange[1];\n    } else {\n      // Case 9: New range starts after the last existing range\n      ranges.push(newRange);\n    }\n\n    return ranges;\n  }\n}\n"
  },
  {
    "path": "src/browser/services/CoreBrowserService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICoreBrowserService } from './Services';\nimport { Emitter, EventUtils } from 'common/Event';\nimport { addDisposableListener } from 'browser/Dom';\nimport { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\n\nexport class CoreBrowserService extends Disposable implements ICoreBrowserService {\n  public serviceBrand: undefined;\n\n  private _isFocused = false;\n  private _cachedIsFocused: boolean | undefined = undefined;\n  private _screenDprMonitor: ScreenDprMonitor;\n\n  private readonly _onDprChange = this._register(new Emitter<number>());\n  public readonly onDprChange = this._onDprChange.event;\n  private readonly _onWindowChange = this._register(new Emitter<Window & typeof globalThis>());\n  public readonly onWindowChange = this._onWindowChange.event;\n\n  constructor(\n    private _textarea: HTMLTextAreaElement,\n    private _window: Window & typeof globalThis,\n    public readonly mainDocument: Document\n  ) {\n    super();\n\n    this._screenDprMonitor = this._register(new ScreenDprMonitor(this._window));\n\n    // Monitor device pixel ratio\n    this._register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w)));\n    this._register(EventUtils.forward(this._screenDprMonitor.onDprChange, this._onDprChange));\n\n    this._register(addDisposableListener(this._textarea, 'focus', () => this._isFocused = true));\n    this._register(addDisposableListener(this._textarea, 'blur', () => this._isFocused = false));\n  }\n\n  public get window(): Window & typeof globalThis {\n    return this._window;\n  }\n\n  public set window(value: Window & typeof globalThis) {\n    if (this._window !== value) {\n      this._window = value;\n      this._onWindowChange.fire(this._window);\n    }\n  }\n\n  public get dpr(): number {\n    return this.window.devicePixelRatio;\n  }\n\n  public get isFocused(): boolean {\n    if (this._cachedIsFocused === undefined) {\n      this._cachedIsFocused = this._isFocused && this._textarea.ownerDocument.hasFocus();\n      queueMicrotask(() => this._cachedIsFocused = undefined);\n    }\n    return this._cachedIsFocused;\n  }\n}\n\n\n/**\n * The screen device pixel ratio monitor allows listening for when the\n * window.devicePixelRatio value changes. This is done not with polling but with\n * the use of window.matchMedia to watch media queries. When the event fires,\n * the listener will be reattached using a different media query to ensure that\n * any further changes will _register.\n *\n * The listener should fire on both window zoom changes and switching to a\n * monitor with a different DPI.\n */\nclass ScreenDprMonitor extends Disposable {\n  private _currentDevicePixelRatio: number;\n  private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;\n  private _resolutionMediaMatchList: MediaQueryList | undefined;\n  private _windowResizeListener = this._register(new MutableDisposable());\n\n  private readonly _onDprChange = this._register(new Emitter<number>());\n  public readonly onDprChange = this._onDprChange.event;\n\n  constructor(private _parentWindow: Window) {\n    super();\n\n    // Initialize listener and dpr value\n    this._outerListener = () => this._setDprAndFireIfDiffers();\n    this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;\n    this._updateDpr();\n\n    // Monitor active window resize\n    this._setWindowResizeListener();\n\n    // Setup additional disposables\n    this._register(toDisposable(() => this.clearListener()));\n  }\n\n\n  public setWindow(parentWindow: Window): void {\n    this._parentWindow = parentWindow;\n    this._setWindowResizeListener();\n    this._setDprAndFireIfDiffers();\n  }\n\n  private _setWindowResizeListener(): void {\n    this._windowResizeListener.value = addDisposableListener(this._parentWindow, 'resize', () => this._setDprAndFireIfDiffers());\n  }\n\n  private _setDprAndFireIfDiffers(): void {\n    if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) {\n      this._onDprChange.fire(this._parentWindow.devicePixelRatio);\n    }\n    this._updateDpr();\n  }\n\n  private _updateDpr(): void {\n    if (!this._outerListener) {\n      return;\n    }\n\n    // Clear listeners for old DPR\n    this._resolutionMediaMatchList?.removeListener(this._outerListener);\n\n    // Add listeners for new DPR\n    this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;\n    this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`);\n    this._resolutionMediaMatchList.addListener(this._outerListener);\n  }\n\n  public clearListener(): void {\n    if (!this._resolutionMediaMatchList || !this._outerListener) {\n      return;\n    }\n    this._resolutionMediaMatchList.removeListener(this._outerListener);\n    this._resolutionMediaMatchList = undefined;\n    this._outerListener = undefined;\n  }\n}\n"
  },
  {
    "path": "src/browser/services/KeyboardService.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IKeyboardService } from 'browser/services/Services';\nimport { evaluateKeyboardEvent } from 'common/input/Keyboard';\nimport { KittyKeyboard, KittyKeyboardEventType, KittyKeyboardFlags } from 'common/input/KittyKeyboard';\nimport { Win32InputMode } from 'common/input/Win32InputMode';\nimport { isMac } from 'common/Platform';\nimport { ICoreService, IOptionsService } from 'common/services/Services';\nimport { IKeyboardResult } from 'common/Types';\n\nexport class KeyboardService implements IKeyboardService {\n  public serviceBrand: undefined;\n\n  private _win32InputMode: Win32InputMode | undefined;\n  private _kittyKeyboard: KittyKeyboard | undefined;\n\n  constructor(\n    @ICoreService private readonly _coreService: ICoreService,\n    @IOptionsService private readonly _optionsService: IOptionsService\n  ) {\n  }\n\n  private _getWin32InputMode(): Win32InputMode {\n    this._win32InputMode ??= new Win32InputMode();\n    return this._win32InputMode;\n  }\n\n  private _getKittyKeyboard(): KittyKeyboard {\n    this._kittyKeyboard ??= new KittyKeyboard();\n    return this._kittyKeyboard;\n  }\n\n  public evaluateKeyDown(event: KeyboardEvent): IKeyboardResult {\n    // Win32 input mode takes priority (most raw)\n    if (this.useWin32InputMode) {\n      return this._getWin32InputMode().evaluateKeyboardEvent(event, true);\n    }\n    const kittyFlags = this._coreService.kittyKeyboard.flags;\n    return this.useKitty\n      ? this._getKittyKeyboard().evaluate(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS)\n      : evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, isMac, this._optionsService.rawOptions.macOptionIsMeta);\n  }\n\n  public evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined {\n    // Win32 input mode sends key up events\n    if (this.useWin32InputMode) {\n      return this._getWin32InputMode().evaluateKeyboardEvent(event, false);\n    }\n    const kittyFlags = this._coreService.kittyKeyboard.flags;\n    if (this.useKitty && (kittyFlags & KittyKeyboardFlags.REPORT_EVENT_TYPES)) {\n      return this._getKittyKeyboard().evaluate(event, kittyFlags, KittyKeyboardEventType.RELEASE);\n    }\n    return undefined;\n  }\n\n  public get useKitty(): boolean {\n    const kittyFlags = this._coreService.kittyKeyboard.flags;\n    return !!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard && KittyKeyboard.shouldUseProtocol(kittyFlags));\n  }\n\n  public get useWin32InputMode(): boolean {\n    return !!(this._optionsService.rawOptions.vtExtensions?.win32InputMode && this._coreService.decPrivateModes.win32InputMode);\n  }\n}\n"
  },
  {
    "path": "src/browser/services/LinkProviderService.ts",
    "content": "import { ILinkProvider, ILinkProviderService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IDisposable } from 'common/Types';\n\nexport class LinkProviderService extends Disposable implements ILinkProviderService {\n  declare public serviceBrand: undefined;\n\n  public readonly linkProviders: ILinkProvider[] = [];\n\n  constructor() {\n    super();\n    this._register(toDisposable(() => this.linkProviders.length = 0));\n  }\n\n  public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {\n    this.linkProviders.push(linkProvider);\n    return {\n      dispose: () => {\n        // Remove the link provider from the list\n        const providerIndex = this.linkProviders.indexOf(linkProvider);\n\n        if (providerIndex !== -1) {\n          this.linkProviders.splice(providerIndex, 1);\n        }\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/browser/services/MouseCoordsService.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { getWindow } from 'browser/Dom';\nimport { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';\nimport { ICharSizeService, IMouseCoordsService, IRenderService } from 'browser/services/Services';\n\nexport class MouseCoordsService implements IMouseCoordsService {\n  public serviceBrand: undefined;\n\n  constructor(\n    @ICharSizeService private readonly _charSizeService: ICharSizeService,\n    @IRenderService private readonly _renderService: IRenderService\n  ) {\n  }\n\n  public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {\n    return getCoords(\n      window,\n      event,\n      element,\n      colCount,\n      rowCount,\n      this._charSizeService.hasValidSize,\n      this._renderService.dimensions.css.cell.width,\n      this._renderService.dimensions.css.cell.height,\n      isSelection\n    );\n  }\n\n  public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {\n    const coords = getCoordsRelativeToElement(getWindow(element), event, element);\n    if (!this._charSizeService.hasValidSize) {\n      return undefined;\n    }\n    coords[0] = Math.min(Math.max(coords[0], 0), this._renderService.dimensions.css.canvas.width - 1);\n    coords[1] = Math.min(Math.max(coords[1], 0), this._renderService.dimensions.css.canvas.height - 1);\n    return {\n      col: Math.floor(coords[0] / this._renderService.dimensions.css.cell.width),\n      row: Math.floor(coords[1] / this._renderService.dimensions.css.cell.height),\n      x: Math.floor(coords[0]),\n      y: Math.floor(coords[1])\n    };\n  }\n}\n"
  },
  {
    "path": "src/browser/services/MouseService.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { MouseService } from 'browser/services/MouseService';\nimport { MouseStateService } from 'common/services/MouseStateService';\nimport { CoreMouseAction, CoreMouseButton } from 'common/Types';\nimport { IBufferService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';\nimport { MockCoreBrowserService, MockRenderService, MockSelectionService } from 'browser/TestUtils.test';\n\nfunction toBytes(s: string | undefined): number[] {\n  if (!s) {\n    return [];\n  }\n  const res: number[] = [];\n  for (let i = 0; i < s.length; ++i) {\n    res.push(s.charCodeAt(i));\n  }\n  return res;\n}\n\n// Minimal mocks for deps that MouseService touches in these tests\nconst bufferService: IBufferService = {\n  buffer: { hasScrollback: true } as any,\n  cols: 500,\n  rows: 500\n} as any;\n\nconst optionsService: IOptionsService = {\n  rawOptions: {\n    logLevel: 'info',\n    fastScrollSensitivity: 1,\n    scrollSensitivity: 1\n  }\n} as any;\n\nconst logService: ILogService = {\n  debug: () => {},\n  info: () => {},\n  warn: () => {},\n  error: () => {}\n} as any;\n\ndescribe('MouseService _triggerMouseEvent', () => {\n  let mouseService: MouseService;\n  let mouseStateService: MouseStateService;\n  let coreService: ICoreService;\n  let reports: string[];\n\n  beforeEach(() => {\n    reports = [];\n    mouseStateService = new MouseStateService();\n    coreService = {\n      triggerDataEvent: (data: string) => reports.push(data),\n      triggerBinaryEvent: (data: string) => reports.push(data),\n      decPrivateModes: { applicationCursorKeys: false }\n    } as any;\n\n    mouseService = new MouseService(\n      new MockRenderService(),\n      {\n        getMouseReportCoords: (_ev: MouseEvent, _el: HTMLElement) => ({ col: 0, row: 0, x: 0, y: 0 })\n      } as any,\n      mouseStateService,\n      coreService,\n      bufferService,\n      optionsService,\n      new MockSelectionService(),\n      logService,\n      new MockCoreBrowserService()\n    );\n  });\n\n  function trigger(e: Parameters<any>[0]): boolean {\n    return (mouseService as any)._triggerMouseEvent(e);\n  }\n\n  it('NONE', () => {\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);\n  });\n\n  it('X10', () => {\n    mouseStateService.activeProtocol = 'X10';\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);\n  });\n\n  it('VT200', () => {\n    mouseStateService.activeProtocol = 'VT200';\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);\n  });\n\n  it('DRAG', () => {\n    mouseStateService.activeProtocol = 'DRAG';\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);\n  });\n\n  it('ANY', () => {\n    mouseStateService.activeProtocol = 'ANY';\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), true);\n    // should not report in any case\n    // invalid button + action combinations\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false);\n    // invalid coords\n    assert.equal(trigger({ col: -1, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 500, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 0, row: -1, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);\n    assert.equal(trigger({ col: 0, row: 500, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);\n  });\n\n  describe('coords', () => {\n    it('DEFAULT encoding', () => {\n      mouseStateService.activeProtocol = 'ANY';\n      for (let i = 0; i < bufferService.cols; ++i) {\n        assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n        if (i > 222) {\n          // supress mouse reports if we are out of addressible range (max. 222)\n          assert.deepEqual(toBytes(reports.pop()), []);\n        } else {\n          assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);\n        }\n      }\n    });\n\n    it('SGR encoding', () => {\n      mouseStateService.activeProtocol = 'ANY';\n      mouseStateService.activeEncoding = 'SGR';\n      for (let i = 0; i < bufferService.cols; ++i) {\n        assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n        assert.deepEqual(reports.pop(), `\\x1b[<0;${i + 1};1M`);\n      }\n    });\n\n    it('SGR_PIXELS encoding', () => {\n      mouseStateService.activeProtocol = 'ANY';\n      mouseStateService.activeEncoding = 'SGR_PIXELS';\n      for (let i = 0; i < 500; ++i) {\n        assert.equal(trigger({ col: 0, row: 0, x: i, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);\n        assert.deepEqual(reports.pop(), `\\x1b[<0;${i};0M`);\n      }\n    });\n  });\n\n  it('eventCodes with modifiers (DEFAULT encoding)', () => {\n    // TODO: implement AUX button tests\n    mouseStateService.activeProtocol = 'ANY';\n    mouseStateService.activeEncoding = 'DEFAULT';\n    // all buttons + down + no modifer\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);\n    assert.deepEqual(reports, ['\\x1b[M !!', '\\x1b[M!!!', '\\x1b[M\"!!', '\\x1b[Ma!!']);\n    reports = [];\n\n    // all buttons + up + no modifier\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);\n    assert.deepEqual(reports, ['\\x1b[M#!!', '\\x1b[M#!!', '\\x1b[M#!!', '\\x1b[M`!!']);\n    reports = [];\n\n    // all buttons + move + no modifier\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);\n    assert.deepEqual(reports, ['\\x1b[M@!!', '\\x1b[MA!!', '\\x1b[MB!!', '\\x1b[MC!!']);\n    reports = [];\n\n    // button none + move + modifiers\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: false, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: true }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: false }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: true }), true);\n    assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: true }), true);\n    assert.deepEqual(reports, ['\\x1b[MS!!', '\\x1b[MK!!', '\\x1b[MG!!', '\\x1b[M[!!', '\\x1b[MO!!', '\\x1b[M_!!']);\n    reports = [];\n  });\n});\n"
  },
  {
    "path": "src/browser/services/MouseService.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { addDisposableListener } from 'browser/Dom';\nimport { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';\nimport { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';\nimport { C0 } from 'common/data/EscapeSequences';\nimport { toDisposable } from 'common/Lifecycle';\nimport { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';\nimport { Gesture, EventType as GestureEventType, IGestureEvent } from 'browser/scrollable/touch';\n\ntype RequestedMouseEvents = Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener | null>;\n\ninterface IMouseBindContext {\n  readonly target: IMouseServiceTarget;\n  readonly focus: () => void;\n  readonly requestedEvents: RequestedMouseEvents;\n}\n\nexport class MouseService implements IMouseService {\n  public serviceBrand: undefined;\n\n  private _lastEvent: ICoreMouseEvent | null = null;\n  private _wheelPartialScroll: number = 0;\n  private _touchScrollAccumulator: number = 0;\n\n  constructor(\n    @IRenderService private readonly _renderService: IRenderService,\n    @IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,\n    @IMouseStateService private readonly _mouseStateService: IMouseStateService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @ISelectionService private readonly _selectionService: ISelectionService,\n    @ILogService private readonly _logService: ILogService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService\n  ) {\n  }\n\n  public bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void {\n    const { element, document } = target;\n\n    /**\n     * Event listener state handling.\n     * We listen to the onProtocolChange event of MouseStateService and put\n     * requested listeners in `requestedEvents`. With this the listeners\n     * have all bits to do the event listener juggling.\n     * Note: 'mousedown' currently is \"always on\" and not managed\n     * by onProtocolChange.\n     */\n    const requestedEvents: RequestedMouseEvents = {\n      mouseup: null,\n      wheel: null,\n      mousedrag: null,\n      mousemove: null\n    };\n    const ctx: IMouseBindContext = { target, focus, requestedEvents };\n    const eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener> = {\n      mouseup: (ev: Event) => this._handleMouseUp(ctx, ev as MouseEvent),\n      wheel: (ev: Event) => this._handleWheel(ctx, ev as WheelEvent),\n      mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),\n      mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)\n    };\n    register(this._mouseStateService.onProtocolChange(events => {\n      this._handleProtocolChange(ctx, eventListeners, events);\n    }));\n    // force initial onProtocolChange so we dont miss early mouse requests\n    this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;\n\n    // Ensure document-level listeners are removed on dispose\n    register(toDisposable(() => {\n      if (requestedEvents.mouseup) {\n        document.removeEventListener('mouseup', requestedEvents.mouseup);\n      }\n      if (requestedEvents.mousedrag) {\n        document.removeEventListener('mousemove', requestedEvents.mousedrag);\n      }\n    }));\n\n    /**\n     * \"Always on\" event listeners.\n     */\n    register(addDisposableListener(element, 'mousedown', (ev: MouseEvent) => this._handleMouseDown(ctx, ev)));\n    register(addDisposableListener(element, 'wheel', (ev: WheelEvent) => this._handlePassiveWheel(ctx, ev), { passive: false }));\n    register(Gesture.addTarget(target.screenElement));\n    register(addDisposableListener(target.screenElement, GestureEventType.START, () => this._handleTouchStart()));\n    register(addDisposableListener(target.screenElement, GestureEventType.CHANGE, (e: IGestureEvent) => this._handleTouchChange(ctx, e)));\n  }\n\n  private _sendEvent(ctx: IMouseBindContext, ev: MouseEvent | WheelEvent): boolean {\n    // Get mouse coordinates\n    const pos = this._mouseCoordsService.getMouseReportCoords(ev as MouseEvent, ctx.target.screenElement);\n    if (!pos) {\n      return false;\n    }\n\n    let but: CoreMouseButton;\n    let action: CoreMouseAction | undefined;\n    switch ((ev as MouseEvent & { overrideType?: string }).overrideType || ev.type) {\n      case 'mousemove':\n        action = CoreMouseAction.MOVE;\n        if (ev.buttons === undefined) {\n          // buttons is not supported on macOS, try to get a value from button instead\n          but = CoreMouseButton.NONE;\n          if (ev.button !== undefined) {\n            but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;\n          }\n        } else {\n          // according to MDN buttons only reports up to button 5 (AUX2)\n          but = ev.buttons & 1 ? CoreMouseButton.LEFT :\n            ev.buttons & 4 ? CoreMouseButton.MIDDLE :\n              ev.buttons & 2 ? CoreMouseButton.RIGHT :\n                CoreMouseButton.NONE; // fallback to NONE\n        }\n        break;\n      case 'mouseup':\n        action = CoreMouseAction.UP;\n        but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;\n        break;\n      case 'mousedown':\n        action = CoreMouseAction.DOWN;\n        but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;\n        break;\n      case 'wheel':\n        if (!this._mouseStateService.allowCustomWheelEvent(ev as WheelEvent)) {\n          return false;\n        }\n        const deltaY = (ev as WheelEvent).deltaY;\n        if (deltaY === 0) {\n          return false;\n        }\n        const lines = this._consumeWheelEvent(\n          ev as WheelEvent,\n          this._renderService?.dimensions?.device?.cell?.height,\n          this._coreBrowserService?.dpr\n        );\n        if (lines === 0) {\n          return false;\n        }\n        action = deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;\n        but = CoreMouseButton.WHEEL;\n        break;\n      default:\n        // dont handle other event types by accident\n        return false;\n    }\n\n    // exit if we cannot determine valid button/action values\n    // do nothing for higher buttons than wheel\n    if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {\n      return false;\n    }\n\n    return this._triggerMouseEvent({\n      col: pos.col,\n      row: pos.row,\n      x: pos.x,\n      y: pos.y,\n      button: but,\n      action,\n      ctrl: ev.ctrlKey,\n      alt: ev.altKey,\n      shift: ev.shiftKey\n    });\n  }\n\n  private _handleMouseUp(ctx: IMouseBindContext, ev: MouseEvent): void {\n    this._sendEvent(ctx, ev);\n    if (!ev.buttons) {\n      // if no other button is held remove global handlers\n      if (ctx.requestedEvents.mouseup) {\n        ctx.target.document.removeEventListener('mouseup', ctx.requestedEvents.mouseup);\n      }\n      if (ctx.requestedEvents.mousedrag) {\n        ctx.target.document.removeEventListener('mousemove', ctx.requestedEvents.mousedrag);\n      }\n    }\n  }\n\n  private _handleWheel(ctx: IMouseBindContext, ev: WheelEvent): false {\n    this._sendEvent(ctx, ev);\n    ev.preventDefault();\n    ev.stopPropagation();\n    return false;\n  }\n\n  private _handleMouseDrag(ctx: IMouseBindContext, ev: MouseEvent): void {\n    // deal only with move while a button is held\n    if (ev.buttons) {\n      this._sendEvent(ctx, ev);\n    }\n  }\n\n  private _handleMouseMove(ctx: IMouseBindContext, ev: MouseEvent): void {\n    // deal only with move without any button\n    if (!ev.buttons) {\n      this._sendEvent(ctx, ev);\n    }\n  }\n\n  private _handleMouseDown(ctx: IMouseBindContext, ev: MouseEvent): void {\n    ev.preventDefault();\n    ctx.focus();\n\n    // Don't send the mouse button to the pty if mouse events are disabled or\n    // if the selection manager is having selection forced (ie. a modifier is\n    // held).\n    if (!this._mouseStateService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) {\n      return;\n    }\n\n    this._sendEvent(ctx, ev);\n\n    // Register additional global handlers which should keep reporting outside\n    // of the terminal element.\n    // Note: Other emulators also do this for 'mousedown' while a button\n    // is held, we currently limit 'mousedown' to the terminal only.\n    if (ctx.requestedEvents.mouseup) {\n      ctx.target.document.addEventListener('mouseup', ctx.requestedEvents.mouseup);\n    }\n    if (ctx.requestedEvents.mousedrag) {\n      ctx.target.document.addEventListener('mousemove', ctx.requestedEvents.mousedrag);\n    }\n  }\n\n  private _handlePassiveWheel(ctx: IMouseBindContext, ev: WheelEvent): false | void {\n    // do nothing, if app side handles wheel itself\n    if (ctx.requestedEvents.wheel) {\n      return;\n    }\n\n    if (!this._mouseStateService.allowCustomWheelEvent(ev)) {\n      return false;\n    }\n\n    if (!this._bufferService.buffer.hasScrollback) {\n      // Convert wheel events into up/down events when the buffer does not have scrollback, this\n      // enables scrolling in apps hosted in the alt buffer such as vim or tmux even when mouse\n      // events are not enabled.\n      // This used implementation used get the actual lines/partial lines scrolled from the\n      // viewport but since moving to the new viewport implementation has been simplified to\n      // simply send a single up or down sequence.\n\n      // Do nothing if there's no vertical scroll\n      const deltaY = ev.deltaY;\n      if (deltaY === 0) {\n        return false;\n      }\n\n      const lines = this._consumeWheelEvent(\n        ev,\n        this._renderService?.dimensions?.device?.cell?.height,\n        this._coreBrowserService?.dpr\n      );\n      if (lines === 0) {\n        ev.preventDefault();\n        ev.stopPropagation();\n        return false;\n      }\n\n      // Construct and send sequences\n      const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');\n      this._coreService.triggerDataEvent(sequence, true);\n      ev.preventDefault();\n      ev.stopPropagation();\n      return false;\n    }\n  }\n\n  private _handleTouchStart(): void {\n    this._touchScrollAccumulator = 0;\n  }\n\n  private _handleTouchChange(ctx: IMouseBindContext, e: IGestureEvent): void {\n    e.preventDefault();\n    e.stopPropagation();\n\n    // When mouse protocol has wheel events active, send as mouse wheel events.\n    if (ctx.requestedEvents.wheel) {\n      this._handleTouchScrollAsWheel(ctx, e);\n      return;\n    }\n\n    // When in alt buffer (no scrollback), send up/down key sequences.\n    if (!this._bufferService.buffer.hasScrollback) {\n      this._handleTouchScrollAsKeys(e);\n      return;\n    }\n\n    // Normal scrollback: delegate to viewport scrolling when available.\n    ctx.target.handleTouchScroll?.(e.translationY);\n  }\n\n  private _handleTouchScrollAsKeys(e: IGestureEvent): void {\n    const cellHeight = this._renderService?.dimensions.css.cell.height;\n    if (!cellHeight) {\n      return;\n    }\n\n    this._touchScrollAccumulator -= e.translationY;\n    const lines = Math.trunc(this._touchScrollAccumulator / cellHeight);\n    if (lines === 0) {\n      return;\n    }\n\n    this._touchScrollAccumulator -= lines * cellHeight;\n    const sequence = C0.ESC\n      + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[')\n      + (lines < 0 ? 'A' : 'B');\n    for (let i = 0; i < Math.abs(lines); i++) {\n      this._coreService.triggerDataEvent(sequence, true);\n    }\n  }\n\n  private _handleTouchScrollAsWheel(ctx: IMouseBindContext, e: IGestureEvent): void {\n    const cellHeight = this._renderService?.dimensions.css.cell.height;\n    if (!cellHeight) {\n      return;\n    }\n\n    this._touchScrollAccumulator -= e.translationY;\n    const lines = Math.trunc(this._touchScrollAccumulator / cellHeight);\n    if (lines === 0) {\n      return;\n    }\n\n    this._touchScrollAccumulator -= lines * cellHeight;\n    const pos = this._mouseCoordsService.getMouseReportCoords(e, ctx.target.screenElement);\n    if (!pos) {\n      return;\n    }\n\n    for (let i = 0; i < Math.abs(lines); i++) {\n      this._triggerMouseEvent({\n        col: pos.col,\n        row: pos.row,\n        x: pos.x,\n        y: pos.y,\n        button: CoreMouseButton.WHEEL,\n        action: lines < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN,\n        ctrl: false,\n        alt: false,\n        shift: false\n      });\n    }\n  }\n\n  public reset(): void {\n    this._lastEvent = null;\n    this._wheelPartialScroll = 0;\n    this._touchScrollAccumulator = 0;\n  }\n\n  private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {\n    const { element, document } = ctx.target;\n    const { requestedEvents } = ctx;\n    // apply global changes on events\n    if (events) {\n      if (this._optionsService.rawOptions.logLevel === 'debug') {\n        this._logService.debug('Binding to mouse events:', this._explainEvents(events));\n      }\n      element.classList.add('enable-mouse-events');\n      this._selectionService.disable();\n    } else {\n      this._logService.debug('Unbinding from mouse events.');\n      element.classList.remove('enable-mouse-events');\n      this._selectionService.enable();\n    }\n\n    // add/remove handlers from requestedEvents\n    if (!(events & CoreMouseEventType.MOVE)) {\n      if (requestedEvents.mousemove) {\n        element.removeEventListener('mousemove', requestedEvents.mousemove);\n      }\n      requestedEvents.mousemove = null;\n    } else if (!requestedEvents.mousemove) {\n      element.addEventListener('mousemove', eventListeners.mousemove);\n      requestedEvents.mousemove = eventListeners.mousemove;\n    }\n\n    if (!(events & CoreMouseEventType.WHEEL)) {\n      if (requestedEvents.wheel) {\n        element.removeEventListener('wheel', requestedEvents.wheel);\n      }\n      requestedEvents.wheel = null;\n    } else if (!requestedEvents.wheel) {\n      element.addEventListener('wheel', eventListeners.wheel, { passive: false });\n      requestedEvents.wheel = eventListeners.wheel;\n    }\n\n    if (!(events & CoreMouseEventType.UP)) {\n      if (requestedEvents.mouseup) {\n        document.removeEventListener('mouseup', requestedEvents.mouseup);\n      }\n      requestedEvents.mouseup = null;\n    } else {\n      requestedEvents.mouseup ??= eventListeners.mouseup;\n    }\n\n    if (!(events & CoreMouseEventType.DRAG)) {\n      if (requestedEvents.mousedrag) {\n        document.removeEventListener('mousemove', requestedEvents.mousedrag);\n      }\n      requestedEvents.mousedrag = null;\n    } else {\n      requestedEvents.mousedrag ??= eventListeners.mousedrag;\n    }\n  }\n\n  private _applyScrollModifier(amount: number, ev: WheelEvent): number {\n    // Multiply the scroll speed when the modifier key is pressed\n    if (ev.altKey || ev.ctrlKey || ev.shiftKey) {\n      return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;\n    }\n    return amount * this._optionsService.rawOptions.scrollSensitivity;\n  }\n\n  /**\n   * Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.\n   * This prevents hyper-sensitive scrolling in alt buffer.\n   */\n  private _consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number {\n    // Do nothing if it's not a vertical scroll event\n    if (ev.deltaY === 0 || ev.shiftKey) {\n      return 0;\n    }\n\n    if (cellHeight === undefined || dpr === undefined) {\n      return 0;\n    }\n\n    const targetWheelEventPixels = cellHeight / dpr;\n    let amount = this._applyScrollModifier(ev.deltaY, ev);\n\n    if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {\n      amount /= (targetWheelEventPixels + 0.0); // Prevent integer division\n\n      const isLikelyTrackpad = Math.abs(ev.deltaY) < 50;\n      if (isLikelyTrackpad) {\n        amount *= 0.3;\n      }\n\n      this._wheelPartialScroll += amount;\n      amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);\n      this._wheelPartialScroll %= 1;\n    } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n      amount *= this._bufferService.rows;\n    }\n    return amount;\n  }\n\n  /**\n   * Triggers a mouse event to be sent.\n   *\n   * Returns true if the event passed all protocol restrictions and a report\n   * was sent, otherwise false. The return value may be used to decide whether\n   * the default event action in the browser component should be omitted.\n   *\n   * Note: The method will change values of the given event object\n   * to fulfill protocol and encoding restrictions.\n   */\n  private _triggerMouseEvent(e: ICoreMouseEvent): boolean {\n    // range check for col/row\n    if (e.col < 0 || e.col >= this._bufferService.cols\n      || e.row < 0 || e.row >= this._bufferService.rows) {\n      return false;\n    }\n\n    // filter nonsense combinations of button + action\n    if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) {\n      return false;\n    }\n    if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) {\n      return false;\n    }\n    if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) {\n      return false;\n    }\n\n    // report 1-based coords\n    e.col++;\n    e.row++;\n\n    // debounce move events at grid or pixel level\n    if (e.action === CoreMouseAction.MOVE\n      && this._lastEvent\n      && this._equalEvents(this._lastEvent, e, this._mouseStateService.isPixelEncoding)\n    ) {\n      return false;\n    }\n\n    // apply protocol restrictions\n    if (!this._mouseStateService.restrictMouseEvent(e)) {\n      return false;\n    }\n\n    // encode report and send\n    const report = this._mouseStateService.encodeMouseEvent(e);\n    if (report) {\n      if (this._mouseStateService.isDefaultEncoding) {\n        this._coreService.triggerBinaryEvent(report);\n      } else {\n        this._coreService.triggerDataEvent(report, true);\n      }\n    }\n\n    this._lastEvent = e;\n    return true;\n  }\n\n  private _explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {\n    return {\n      down: !!(events & CoreMouseEventType.DOWN),\n      up: !!(events & CoreMouseEventType.UP),\n      drag: !!(events & CoreMouseEventType.DRAG),\n      move: !!(events & CoreMouseEventType.MOVE),\n      wheel: !!(events & CoreMouseEventType.WHEEL)\n    };\n  }\n\n  private _equalEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent, pixels: boolean): boolean {\n    if (pixels) {\n      if (e1.x !== e2.x) return false;\n      if (e1.y !== e2.y) return false;\n    } else {\n      if (e1.col !== e2.col) return false;\n      if (e1.row !== e2.row) return false;\n    }\n    if (e1.button !== e2.button) return false;\n    if (e1.action !== e2.action) return false;\n    if (e1.ctrl !== e2.ctrl) return false;\n    if (e1.alt !== e2.alt) return false;\n    if (e1.shift !== e2.shift) return false;\n    return true;\n  }\n\n}\n"
  },
  {
    "path": "src/browser/services/RenderService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { RenderDebouncer } from 'browser/RenderDebouncer';\nimport { IRenderDebouncerWithCallback } from 'browser/Types';\nimport { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';\nimport { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';\nimport { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\nimport { DebouncedIdleTask } from 'common/TaskQueue';\nimport { IBufferService, ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\ninterface ISelectionState {\n  start: [number, number] | undefined;\n  end: [number, number] | undefined;\n  columnSelectMode: boolean;\n}\n\nconst enum Constants {\n  SYNCHRONIZED_OUTPUT_TIMEOUT_MS = 1000\n}\n\nexport class RenderService extends Disposable implements IRenderService {\n  public serviceBrand: undefined;\n\n  private _renderer: MutableDisposable<IRenderer> = this._register(new MutableDisposable());\n  private _renderDebouncer: IRenderDebouncerWithCallback;\n  private _pausedResizeTask: DebouncedIdleTask;\n  private _observerDisposable = this._register(new MutableDisposable());\n\n  private _isPaused: boolean = false;\n  private _needsFullRefresh: boolean = false;\n  private _isNextRenderRedrawOnly: boolean = true;\n  private _needsSelectionRefresh: boolean = false;\n  private _canvasWidth: number = 0;\n  private _canvasHeight: number = 0;\n  private _syncOutputHandler: SynchronizedOutputHandler;\n  private _selectionState: ISelectionState = {\n    start: undefined,\n    end: undefined,\n    columnSelectMode: false\n  };\n\n  private readonly _onDimensionsChange = this._register(new Emitter<IRenderDimensions>());\n  public readonly onDimensionsChange = this._onDimensionsChange.event;\n  private readonly _onRenderedViewportChange = this._register(new Emitter<{ start: number, end: number }>());\n  public readonly onRenderedViewportChange = this._onRenderedViewportChange.event;\n  private readonly _onRender = this._register(new Emitter<{ start: number, end: number }>());\n  public readonly onRender = this._onRender.event;\n  private readonly _onRefreshRequest = this._register(new Emitter<{ start: number, end: number }>());\n  public readonly onRefreshRequest = this._onRefreshRequest.event;\n\n  public get dimensions(): IRenderDimensions { return this._renderer.value!.dimensions; }\n\n  constructor(\n    private _rowCount: number,\n    screenElement: HTMLElement,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @ILogService private readonly _logService: ILogService,\n    @ICharSizeService private readonly _charSizeService: ICharSizeService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @IDecorationService decorationService: IDecorationService,\n    @IBufferService bufferService: IBufferService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n    @IThemeService themeService: IThemeService\n  ) {\n    super();\n\n    this._pausedResizeTask = new DebouncedIdleTask(this._logService);\n\n    this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end), this._coreBrowserService);\n    this._register(this._renderDebouncer);\n\n    this._syncOutputHandler = new SynchronizedOutputHandler(\n      this._coreBrowserService,\n      this._coreService,\n      () => this._fullRefresh()\n    );\n    this._register(toDisposable(() => this._syncOutputHandler.dispose()));\n\n    this._register(this._coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange()));\n\n    this._register(bufferService.onResize(() => this._fullRefresh()));\n    this._register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear()));\n    this._register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));\n    this._register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged()));\n\n    // Do a full refresh whenever any decoration is added or removed. This may not actually result\n    // in changes but since decorations should be used sparingly or added/removed all in the same\n    // frame this should have minimal performance impact.\n    this._register(decorationService.onDecorationRegistered(() => this._fullRefresh()));\n    this._register(decorationService.onDecorationRemoved(() => this._fullRefresh()));\n\n    // Clear the renderer when the a change that could affect glyphs occurs\n    this._register(this._optionsService.onMultipleOptionChange([\n      'drawBoldTextInBrightColors',\n      'letterSpacing',\n      'lineHeight',\n      'fontFamily',\n      'fontSize',\n      'fontWeight',\n      'fontWeightBold',\n      'minimumContrastRatio',\n      'rescaleOverlappingGlyphs'\n    ], () => {\n      this.clear();\n      this.handleResize(bufferService.cols, bufferService.rows);\n      this._fullRefresh();\n    }));\n\n    // Refresh the cursor line when the cursor changes\n    this._register(this._optionsService.onMultipleOptionChange([\n      'cursorBlink',\n      'cursorStyle'\n    ], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, undefined, true)));\n\n    this._register(themeService.onChangeColors(() => this._fullRefresh()));\n\n    this._registerIntersectionObserver(this._coreBrowserService.window, screenElement);\n    this._register(this._coreBrowserService.onWindowChange((w) => this._registerIntersectionObserver(w, screenElement)));\n  }\n\n  private _registerIntersectionObserver(w: Window & typeof globalThis, screenElement: HTMLElement): void {\n    // Detect whether IntersectionObserver is detected and enable renderer pause\n    // and resume based on terminal visibility if so\n    if ('IntersectionObserver' in w) {\n      const observer = new w.IntersectionObserver(e => this._handleIntersectionChange(e[e.length - 1]), { threshold: 0 });\n      observer.observe(screenElement);\n      this._observerDisposable.value = toDisposable(() => observer.disconnect());\n    }\n  }\n\n  private _handleIntersectionChange(entry: IntersectionObserverEntry): void {\n    this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;\n    this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused);\n\n    // Terminal was hidden on open\n    if (!this._isPaused && !this._charSizeService.hasValidSize) {\n      this._charSizeService.measure();\n    }\n\n    if (!this._isPaused && this._needsFullRefresh) {\n      this._pausedResizeTask.flush();\n      this.refreshRows(0, this._rowCount - 1);\n      this._needsFullRefresh = false;\n    }\n  }\n\n  public refreshRows(start: number, end: number, sync: boolean = false, isRedrawOnly: boolean = false): void {\n    if (this._isPaused) {\n      this._needsFullRefresh = true;\n      return;\n    }\n\n    if (this._coreService.decPrivateModes.synchronizedOutput) {\n      this._syncOutputHandler.bufferRows(start, end);\n      return;\n    }\n\n    const buffered = this._syncOutputHandler.flush();\n    if (buffered) {\n      start = Math.min(start, buffered.start);\n      end = Math.max(end, buffered.end);\n    }\n\n    if (!isRedrawOnly) {\n      this._isNextRenderRedrawOnly = false;\n    }\n\n    if (sync) {\n      this._renderRows(start, end);\n    } else {\n      this._renderDebouncer.refresh(start, end, this._rowCount);\n    }\n  }\n\n  private _renderRows(start: number, end: number): void {\n    if (!this._renderer.value) {\n      return;\n    }\n\n    // Skip rendering if synchronized output mode is enabled. This check must happen here\n    // (in addition to refreshRows) to handle renders that were queued before the mode was enabled.\n    if (this._coreService.decPrivateModes.synchronizedOutput) {\n      this._syncOutputHandler.bufferRows(start, end);\n      return;\n    }\n\n    // Since this is debounced, a resize event could have happened between the time a refresh was\n    // requested and when this triggers. Clamp the values of start and end to ensure they're valid\n    // given the current viewport state.\n    start = Math.min(start, this._rowCount - 1);\n    end = Math.min(end, this._rowCount - 1);\n\n    // Render\n    this._renderer.value.renderRows(start, end);\n\n    // Update selection if needed\n    if (this._needsSelectionRefresh) {\n      this._renderer.value.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode);\n      this._needsSelectionRefresh = false;\n    }\n\n    // Fire render event only if it was not a redraw\n    if (!this._isNextRenderRedrawOnly) {\n      this._onRenderedViewportChange.fire({ start, end });\n    }\n    this._onRender.fire({ start, end });\n    this._isNextRenderRedrawOnly = true;\n  }\n\n  public resize(cols: number, rows: number): void {\n    this._rowCount = rows;\n    this._fireOnCanvasResize();\n  }\n\n  private _handleOptionsChanged(): void {\n    if (!this._renderer.value) {\n      return;\n    }\n    this.refreshRows(0, this._rowCount - 1);\n    this._fireOnCanvasResize();\n  }\n\n  private _fireOnCanvasResize(): void {\n    if (!this._renderer.value) {\n      return;\n    }\n    // Don't fire the event if the dimensions haven't changed\n    if (this._renderer.value.dimensions.css.canvas.width === this._canvasWidth && this._renderer.value.dimensions.css.canvas.height === this._canvasHeight) {\n      return;\n    }\n    this._onDimensionsChange.fire(this._renderer.value.dimensions);\n  }\n\n  public hasRenderer(): boolean {\n    return !!this._renderer.value;\n  }\n\n  public setRenderer(renderer: IRenderer): void {\n    this._renderer.value = renderer;\n    // If the value was not set, the terminal is being disposed so ignore it\n    if (this._renderer.value) {\n      this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, e.sync, true));\n\n      // Force a refresh\n      this._needsSelectionRefresh = true;\n      this._fullRefresh();\n    }\n  }\n\n  public addRefreshCallback(callback: FrameRequestCallback): number {\n    return this._renderDebouncer.addRefreshCallback(callback);\n  }\n\n  private _fullRefresh(): void {\n    if (this._isPaused) {\n      this._needsFullRefresh = true;\n    } else {\n      this.refreshRows(0, this._rowCount - 1);\n    }\n  }\n\n  public clearTextureAtlas(): void {\n    if (!this._renderer.value) {\n      return;\n    }\n    this._renderer.value.clearTextureAtlas?.();\n    this._fullRefresh();\n  }\n\n  public handleDevicePixelRatioChange(): void {\n    // Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable\n    // when devicePixelRatio changes\n    this._charSizeService.measure();\n\n    if (!this._renderer.value) {\n      return;\n    }\n    this._renderer.value.handleDevicePixelRatioChange();\n    this.refreshRows(0, this._rowCount - 1);\n  }\n\n  public handleResize(cols: number, rows: number): void {\n    if (!this._renderer.value) {\n      return;\n    }\n    if (this._isPaused) {\n      this._pausedResizeTask.set(() => this._renderer.value?.handleResize(cols, rows));\n    } else {\n      this._renderer.value.handleResize(cols, rows);\n    }\n    this._fullRefresh();\n  }\n\n  // TODO: Is this useful when we have onResize?\n  public handleCharSizeChanged(): void {\n    this._renderer.value?.handleCharSizeChanged();\n  }\n\n  public handleBlur(): void {\n    this._renderer.value?.handleBlur();\n  }\n\n  public handleFocus(): void {\n    this._renderer.value?.handleFocus();\n  }\n\n  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {\n    this._selectionState.start = start;\n    this._selectionState.end = end;\n    this._selectionState.columnSelectMode = columnSelectMode;\n    this._renderer.value?.handleSelectionChanged(start, end, columnSelectMode);\n  }\n\n  public handleCursorMove(): void {\n    this._renderer.value?.handleCursorMove();\n  }\n\n  public clear(): void {\n    this._renderer.value?.clear();\n  }\n}\n\n/**\n * Buffers row refresh requests during synchronized output mode (DEC mode 2026).\n * When the mode is disabled, the accumulated row range is flushed for rendering.\n * A safety timeout ensures rendering occurs even if the end sequence is not received.\n */\nclass SynchronizedOutputHandler {\n  private _start: number = 0;\n  private _end: number = 0;\n  private _timeout: number | undefined;\n  private _isBuffering: boolean = false;\n\n  constructor(\n    private readonly _coreBrowserService: ICoreBrowserService,\n    private readonly _coreService: ICoreService,\n    private readonly _onTimeout: () => void\n  ) {}\n\n  public bufferRows(start: number, end: number): void {\n    if (!this._isBuffering) {\n      this._start = start;\n      this._end = end;\n      this._isBuffering = true;\n    } else {\n      this._start = Math.min(this._start, start);\n      this._end = Math.max(this._end, end);\n    }\n\n    this._timeout ??= this._coreBrowserService.window.setTimeout(() => {\n      this._timeout = undefined;\n      this._coreService.decPrivateModes.synchronizedOutput = false;\n      this._onTimeout();\n    }, Constants.SYNCHRONIZED_OUTPUT_TIMEOUT_MS);\n  }\n\n  public flush(): { start: number, end: number } | undefined {\n    if (this._timeout !== undefined) {\n      this._coreBrowserService.window.clearTimeout(this._timeout);\n      this._timeout = undefined;\n    }\n\n    if (!this._isBuffering) {\n      return undefined;\n    }\n\n    const result = { start: this._start, end: this._end };\n    this._isBuffering = false;\n    return result;\n  }\n\n  public dispose(): void {\n    if (this._timeout !== undefined) {\n      this._coreBrowserService.window.clearTimeout(this._timeout);\n      this._timeout = undefined;\n    }\n  }\n}\n"
  },
  {
    "path": "src/browser/services/SelectionService.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { SelectionService, SelectionMode } from './SelectionService';\nimport { SelectionModel } from 'browser/selection/SelectionModel';\nimport { IBufferLine } from 'common/Types';\nimport { MockBufferService, MockOptionsService, MockCoreService, createCellData } from 'common/TestUtils.test';\nimport { BufferLine } from 'common/buffer/BufferLine';\nimport { IBufferService, IOptionsService } from 'common/services/Services';\nimport { MockCoreBrowserService, MockMouseService, MockRenderService } from 'browser/TestUtils.test';\nimport { CellData } from 'common/buffer/CellData';\nimport { IBuffer } from 'common/buffer/Types';\nimport { IRenderService } from 'browser/services/Services';\n\nclass TestSelectionService extends SelectionService {\n  constructor(\n    bufferService: IBufferService,\n    optionsService: IOptionsService,\n    renderService: IRenderService\n  ) {\n    super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService, new MockCoreBrowserService());\n  }\n\n  public get model(): SelectionModel { return this._model; }\n\n  public set selectionMode(mode: SelectionMode) { this._activeSelectionMode = mode; }\n\n  public selectLineAt(line: number): void { this._selectLineAt(line); }\n  public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }\n  public areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean { return this._areCoordsInSelection(coords, start, end); }\n\n  // Disable DOM interaction\n  public enable(): void {}\n  public disable(): void {}\n  public refresh(): void {}\n}\n\ndescribe('SelectionService', () => {\n  let buffer: IBuffer;\n  let bufferService: IBufferService;\n  let optionsService: IOptionsService;\n  let selectionService: TestSelectionService;\n\n  beforeEach(() => {\n    optionsService = new MockOptionsService();\n    bufferService = new MockBufferService(20, 20, optionsService);\n    buffer = bufferService.buffer;\n    const renderService = new MockRenderService();\n    renderService.dimensions.css.canvas.height = 10 * 20;\n    renderService.dimensions.css.canvas.width = 10 * 20;\n    selectionService = new TestSelectionService(bufferService, optionsService, renderService);\n  });\n\n  function stringToRow(text: string): IBufferLine {\n    const result = new BufferLine(text.length);\n    for (let i = 0; i < text.length; i++) {\n      result.setCell(i, createCellData(0, text.charAt(i), 1));\n    }\n    return result;\n  }\n\n  function stringArrayToRow(chars: string[]): IBufferLine {\n    const line = new BufferLine(chars.length);\n    chars.map((c, idx) => line.setCell(idx, createCellData(0, c, 1)));\n    return line;\n  }\n\n  describe('_selectWordAt', () => {\n    it('should expand selection for normal width chars', () => {\n      buffer.lines.set(0, stringToRow('foo bar'));\n      selectionService.selectWordAt([0, 0]);\n      assert.equal(selectionService.selectionText, 'foo');\n      selectionService.selectWordAt([1, 0]);\n      assert.equal(selectionService.selectionText, 'foo');\n      selectionService.selectWordAt([2, 0]);\n      assert.equal(selectionService.selectionText, 'foo');\n      selectionService.selectWordAt([3, 0]);\n      assert.equal(selectionService.selectionText, ' ');\n      selectionService.selectWordAt([4, 0]);\n      assert.equal(selectionService.selectionText, 'bar');\n      selectionService.selectWordAt([5, 0]);\n      assert.equal(selectionService.selectionText, 'bar');\n      selectionService.selectWordAt([6, 0]);\n      assert.equal(selectionService.selectionText, 'bar');\n    });\n    it('should expand selection for whitespace', () => {\n      buffer.lines.set(0, stringToRow('a   b'));\n      selectionService.selectWordAt([0, 0]);\n      assert.equal(selectionService.selectionText, 'a');\n      selectionService.selectWordAt([1, 0]);\n      assert.equal(selectionService.selectionText, '   ');\n      selectionService.selectWordAt([2, 0]);\n      assert.equal(selectionService.selectionText, '   ');\n      selectionService.selectWordAt([3, 0]);\n      assert.equal(selectionService.selectionText, '   ');\n      selectionService.selectWordAt([4, 0]);\n      assert.equal(selectionService.selectionText, 'b');\n    });\n    it('should expand selection for wide characters', () => {\n      // Wide characters use a special format\n      const data: [number, string, number, number][] = [\n        [0, '中', 2, '中'.charCodeAt(0)],\n        [0, '', 0, 0],\n        [0, '文', 2, '文'.charCodeAt(0)],\n        [0, '', 0, 0],\n        [0, ' ', 1, ' '.charCodeAt(0)],\n        [0, 'a', 1, 'a'.charCodeAt(0)],\n        [0, '中', 2, '中'.charCodeAt(0)],\n        [0, '', 0, 0],\n        [0, '文', 2, '文'.charCodeAt(0)],\n        [0, '', 0, ''.charCodeAt(0)],\n        [0, 'b', 1, 'b'.charCodeAt(0)],\n        [0, ' ', 1, ' '.charCodeAt(0)],\n        [0, 'f', 1, 'f'.charCodeAt(0)],\n        [0, 'o', 1, 'o'.charCodeAt(0)],\n        [0, 'o', 1, 'o'.charCodeAt(0)]\n      ];\n      const line = new BufferLine(data.length);\n      for (let i = 0; i < data.length; ++i) line.setCell(i, CellData.fromCharData(data[i]));\n      buffer.lines.set(0, line);\n      // Ensure wide characters take up 2 columns\n      selectionService.selectWordAt([0, 0]);\n      assert.equal(selectionService.selectionText, '中文');\n      selectionService.selectWordAt([1, 0]);\n      assert.equal(selectionService.selectionText, '中文');\n      selectionService.selectWordAt([2, 0]);\n      assert.equal(selectionService.selectionText, '中文');\n      selectionService.selectWordAt([3, 0]);\n      assert.equal(selectionService.selectionText, '中文');\n      selectionService.selectWordAt([4, 0]);\n      assert.equal(selectionService.selectionText, ' ');\n      // Ensure wide characters work when wrapped in normal width characters\n      selectionService.selectWordAt([5, 0]);\n      assert.equal(selectionService.selectionText, 'a中文b');\n      selectionService.selectWordAt([6, 0]);\n      assert.equal(selectionService.selectionText, 'a中文b');\n      selectionService.selectWordAt([7, 0]);\n      assert.equal(selectionService.selectionText, 'a中文b');\n      selectionService.selectWordAt([8, 0]);\n      assert.equal(selectionService.selectionText, 'a中文b');\n      selectionService.selectWordAt([9, 0]);\n      assert.equal(selectionService.selectionText, 'a中文b');\n      selectionService.selectWordAt([10, 0]);\n      assert.equal(selectionService.selectionText, 'a中文b');\n      selectionService.selectWordAt([11, 0]);\n      assert.equal(selectionService.selectionText, ' ');\n      // Ensure normal width characters work fine in a line containing wide characters\n      selectionService.selectWordAt([12, 0]);\n      assert.equal(selectionService.selectionText, 'foo');\n      selectionService.selectWordAt([13, 0]);\n      assert.equal(selectionService.selectionText, 'foo');\n      selectionService.selectWordAt([14, 0]);\n      assert.equal(selectionService.selectionText, 'foo');\n    });\n    it('should select up to non-path characters that are commonly adjacent to paths', () => {\n      buffer.lines.set(0, stringToRow('(cd)[ef]{gh}\\'ij\"'));\n      selectionService.selectWordAt([0, 0]);\n      assert.equal(selectionService.selectionText, '(cd');\n      selectionService.selectWordAt([1, 0]);\n      assert.equal(selectionService.selectionText, 'cd');\n      selectionService.selectWordAt([2, 0]);\n      assert.equal(selectionService.selectionText, 'cd');\n      selectionService.selectWordAt([3, 0]);\n      assert.equal(selectionService.selectionText, 'cd)');\n      selectionService.selectWordAt([4, 0]);\n      assert.equal(selectionService.selectionText, '[ef');\n      selectionService.selectWordAt([5, 0]);\n      assert.equal(selectionService.selectionText, 'ef');\n      selectionService.selectWordAt([6, 0]);\n      assert.equal(selectionService.selectionText, 'ef');\n      selectionService.selectWordAt([7, 0]);\n      assert.equal(selectionService.selectionText, 'ef]');\n      selectionService.selectWordAt([8, 0]);\n      assert.equal(selectionService.selectionText, '{gh');\n      selectionService.selectWordAt([9, 0]);\n      assert.equal(selectionService.selectionText, 'gh');\n      selectionService.selectWordAt([10, 0]);\n      assert.equal(selectionService.selectionText, 'gh');\n      selectionService.selectWordAt([11, 0]);\n      assert.equal(selectionService.selectionText, 'gh}');\n      selectionService.selectWordAt([12, 0]);\n      assert.equal(selectionService.selectionText, '\\'ij');\n      selectionService.selectWordAt([13, 0]);\n      assert.equal(selectionService.selectionText, 'ij');\n      selectionService.selectWordAt([14, 0]);\n      assert.equal(selectionService.selectionText, 'ij');\n      selectionService.selectWordAt([15, 0]);\n      assert.equal(selectionService.selectionText, 'ij\"');\n    });\n    it('should expand upwards or downards for wrapped lines', () => {\n      buffer.lines.set(0, stringToRow('                 foo'));\n      buffer.lines.set(1, stringToRow('bar                 '));\n      buffer.lines.get(1)!.isWrapped = true;\n      selectionService.selectWordAt([1, 1]);\n      assert.equal(selectionService.selectionText, 'foobar');\n      selectionService.model.clearSelection();\n      selectionService.selectWordAt([18, 0]);\n      assert.equal(selectionService.selectionText, 'foobar');\n    });\n    it('should expand both upwards and downwards for word wrapped over many lines', () => {\n      const expectedText = 'fooaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbccccccccccccccccccccbar';\n      buffer.lines.set(0, stringToRow('                 foo'));\n      buffer.lines.set(1, stringToRow('aaaaaaaaaaaaaaaaaaaa'));\n      buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbb'));\n      buffer.lines.set(3, stringToRow('cccccccccccccccccccc'));\n      buffer.lines.set(4, stringToRow('bar                 '));\n      buffer.lines.get(1)!.isWrapped = true;\n      buffer.lines.get(2)!.isWrapped = true;\n      buffer.lines.get(3)!.isWrapped = true;\n      buffer.lines.get(4)!.isWrapped = true;\n      selectionService.selectWordAt([18, 0]);\n      assert.equal(selectionService.selectionText, expectedText);\n      selectionService.model.clearSelection();\n      selectionService.selectWordAt([10, 1]);\n      assert.equal(selectionService.selectionText, expectedText);\n      selectionService.model.clearSelection();\n      selectionService.selectWordAt([10, 2]);\n      assert.equal(selectionService.selectionText, expectedText);\n      selectionService.model.clearSelection();\n      selectionService.selectWordAt([10, 3]);\n      assert.equal(selectionService.selectionText, expectedText);\n      selectionService.model.clearSelection();\n      selectionService.selectWordAt([1, 4]);\n      assert.equal(selectionService.selectionText, expectedText);\n    });\n    describe('emoji', () => {\n      it('should treat a single emoji as a word when wrapped in spaces', () => {\n        buffer.lines.set(0, stringToRow(' ⚽ a')); // The a is here to prevent the space being trimmed in selectionText\n        selectionService.selectWordAt([0, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([1, 0]);\n        assert.equal(selectionService.selectionText, '⚽');\n        selectionService.selectWordAt([2, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n      });\n      it('should treat multiple emojis as a word when wrapped in spaces', () => {\n        buffer.lines.set(0, stringToRow(' ⚽⚽ a')); // The a is here to prevent the space being trimmed in selectionText\n        selectionService.selectWordAt([0, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([1, 0]);\n        assert.equal(selectionService.selectionText, '⚽⚽');\n        selectionService.selectWordAt([2, 0]);\n        assert.equal(selectionService.selectionText, '⚽⚽');\n        selectionService.selectWordAt([3, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n      });\n      it('should treat emojis using the zero-width-joiner as a single word', () => {\n        // Note that the first 3 emojis include the invisible ZWJ char\n        buffer.lines.set(0, stringArrayToRow([\n          ' ', '👨‍', '👩‍', '👧‍', '👦', ' ', 'a'\n        ])); // The a is here to prevent the space being trimmed in selectionText\n        selectionService.selectWordAt([0, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        // ZWJ emojis do not combine in the terminal so the family emoji used here consumed 4 cells\n        // The selection text should retain ZWJ chars despite not combining on the terminal\n        selectionService.selectWordAt([1, 0]);\n        assert.equal(selectionService.selectionText, '👨‍👩‍👧‍👦');\n        selectionService.selectWordAt([2, 0]);\n        assert.equal(selectionService.selectionText, '👨‍👩‍👧‍👦');\n        selectionService.selectWordAt([3, 0]);\n        assert.equal(selectionService.selectionText, '👨‍👩‍👧‍👦');\n        selectionService.selectWordAt([4, 0]);\n        assert.equal(selectionService.selectionText, '👨‍👩‍👧‍👦');\n        selectionService.selectWordAt([5, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n      });\n      it('should treat emojis and characters joined together as a word', () => {\n        buffer.lines.set(0, stringToRow(' ⚽ab cd⚽ ef⚽gh')); // The a is here to prevent the space being trimmed in selectionText\n        selectionService.selectWordAt([0, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([1, 0]);\n        assert.equal(selectionService.selectionText, '⚽ab');\n        selectionService.selectWordAt([2, 0]);\n        assert.equal(selectionService.selectionText, '⚽ab');\n        selectionService.selectWordAt([3, 0]);\n        assert.equal(selectionService.selectionText, '⚽ab');\n        selectionService.selectWordAt([4, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([5, 0]);\n        assert.equal(selectionService.selectionText, 'cd⚽');\n        selectionService.selectWordAt([6, 0]);\n        assert.equal(selectionService.selectionText, 'cd⚽');\n        selectionService.selectWordAt([7, 0]);\n        assert.equal(selectionService.selectionText, 'cd⚽');\n        selectionService.selectWordAt([8, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([9, 0]);\n        assert.equal(selectionService.selectionText, 'ef⚽gh');\n        selectionService.selectWordAt([10, 0]);\n        assert.equal(selectionService.selectionText, 'ef⚽gh');\n        selectionService.selectWordAt([11, 0]);\n        assert.equal(selectionService.selectionText, 'ef⚽gh');\n        selectionService.selectWordAt([12, 0]);\n        assert.equal(selectionService.selectionText, 'ef⚽gh');\n        selectionService.selectWordAt([13, 0]);\n        assert.equal(selectionService.selectionText, 'ef⚽gh');\n      });\n      it('should treat complex emojis and characters joined together as a word', () => {\n        // This emoji is the flag for England and is made up of: 1F3F4 E0067 E0062 E0065 E006E E0067 E007F\n        buffer.lines.set(0, stringArrayToRow([\n          ' ', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', 'a', 'b', ' ', 'c', 'd', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', ' ', 'e', 'f', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', 'g', 'h', ' ', 'a'\n        ])); // The a is here to prevent the space being trimmed in selectionText\n        selectionService.selectWordAt([0, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([1, 0]);\n        assert.equal(selectionService.selectionText, '🏴󠁧󠁢󠁥󠁮󠁧󠁿ab');\n        selectionService.selectWordAt([2, 0]);\n        assert.equal(selectionService.selectionText, '🏴󠁧󠁢󠁥󠁮󠁧󠁿ab');\n        selectionService.selectWordAt([3, 0]);\n        assert.equal(selectionService.selectionText, '🏴󠁧󠁢󠁥󠁮󠁧󠁿ab');\n        selectionService.selectWordAt([4, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([5, 0]);\n        assert.equal(selectionService.selectionText, 'cd🏴󠁧󠁢󠁥󠁮󠁧󠁿');\n        selectionService.selectWordAt([6, 0]);\n        assert.equal(selectionService.selectionText, 'cd🏴󠁧󠁢󠁥󠁮󠁧󠁿');\n        selectionService.selectWordAt([7, 0]);\n        assert.equal(selectionService.selectionText, 'cd🏴󠁧󠁢󠁥󠁮󠁧󠁿');\n        selectionService.selectWordAt([8, 0]);\n        assert.equal(selectionService.selectionText, ' ');\n        selectionService.selectWordAt([9, 0]);\n        assert.equal(selectionService.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');\n        selectionService.selectWordAt([10, 0]);\n        assert.equal(selectionService.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');\n        selectionService.selectWordAt([11, 0]);\n        assert.equal(selectionService.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');\n        selectionService.selectWordAt([12, 0]);\n        assert.equal(selectionService.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');\n        selectionService.selectWordAt([13, 0]);\n        assert.equal(selectionService.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');\n      });\n    });\n  });\n\n  describe('_selectLineAt', () => {\n    it('should select the entire line', () => {\n      buffer.lines.set(0, stringToRow('foo bar'));\n      selectionService.selectLineAt(0);\n      assert.equal(selectionService.selectionText, 'foo bar', 'The selected text is correct');\n      assert.deepEqual(selectionService.model.selectionStart, [0, 0]);\n      assert.deepEqual(selectionService.model.selectionEnd, undefined);\n      assert.deepEqual(selectionService.model.selectionStartLength, 20);\n      assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);\n      assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 0], 'The actual selection spans the entire column');\n    });\n    it('should select the entire wrapped line', () => {\n      buffer.lines.set(0, stringToRow('foo'));\n      const line2 = stringToRow('bar');\n      line2.isWrapped = true;\n      buffer.lines.set(1, line2);\n      selectionService.selectLineAt(0);\n      assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct');\n      assert.deepEqual(selectionService.model.selectionStart, [0, 0]);\n      assert.deepEqual(selectionService.model.selectionEnd, undefined);\n      assert.deepEqual(selectionService.model.selectionStartLength, 40);\n      assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);\n      assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1], 'The actual selection spans the entire column');\n    });\n  });\n\n  describe('selectAll', () => {\n    it('should select the entire buffer, beyond the viewport', () => {\n      bufferService.resize(20, 5);\n      buffer.lines.set(0, stringToRow('1'));\n      buffer.lines.set(1, stringToRow('2'));\n      buffer.lines.set(2, stringToRow('3'));\n      buffer.lines.set(3, stringToRow('4'));\n      buffer.lines.set(4, stringToRow('5'));\n      selectionService.selectAll();\n      assert.equal(selectionService.selectionText, '1\\n2\\n3\\n4\\n5');\n    });\n  });\n\n  describe('selectLines', () => {\n    it('should select a single line', () => {\n      buffer.lines.length = 3;\n      buffer.lines.set(0, stringToRow('1'));\n      buffer.lines.set(1, stringToRow('2'));\n      buffer.lines.set(2, stringToRow('3'));\n      selectionService.selectLines(1, 1);\n      assert.deepEqual(selectionService.model.finalSelectionStart, [0, 1]);\n      assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1]);\n    });\n    it('should select multiple lines', () => {\n      buffer.lines.length = 5;\n      buffer.lines.set(0, stringToRow('1'));\n      buffer.lines.set(1, stringToRow('2'));\n      buffer.lines.set(2, stringToRow('3'));\n      buffer.lines.set(3, stringToRow('4'));\n      buffer.lines.set(4, stringToRow('5'));\n      selectionService.selectLines(1, 3);\n      assert.deepEqual(selectionService.model.finalSelectionStart, [0, 1]);\n      assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 3]);\n    });\n    it('should select the to the start when requesting a negative row', () => {\n      buffer.lines.length = 2;\n      buffer.lines.set(0, stringToRow('1'));\n      buffer.lines.set(1, stringToRow('2'));\n      selectionService.selectLines(-1, 0);\n      assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);\n      assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 0]);\n    });\n    it('should select the to the end when requesting beyond the final row', () => {\n      buffer.lines.length = 2;\n      buffer.lines.set(0, stringToRow('1'));\n      buffer.lines.set(1, stringToRow('2'));\n      selectionService.selectLines(1, 2);\n      assert.deepEqual(selectionService.model.finalSelectionStart, [0, 1]);\n      assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1]);\n    });\n  });\n\n  describe('hasSelection', () => {\n    it('should return whether there is a selection', () => {\n      selectionService.model.selectionStart = [0, 0];\n      selectionService.model.selectionStartLength = 0;\n      assert.equal(selectionService.hasSelection, false);\n      selectionService.model.selectionEnd = [0, 0];\n      assert.equal(selectionService.hasSelection, false);\n      selectionService.model.selectionEnd = [1, 0];\n      assert.equal(selectionService.hasSelection, true);\n      selectionService.model.selectionEnd = [0, 1];\n      assert.equal(selectionService.hasSelection, true);\n      selectionService.model.selectionEnd = [1, 1];\n      assert.equal(selectionService.hasSelection, true);\n    });\n  });\n\n  describe('column selection', () => {\n    it('should select a column of text', () => {\n      buffer.lines.length = 3;\n      buffer.lines.set(0, stringToRow('abcdefghij'));\n      buffer.lines.set(1, stringToRow('klmnopqrst'));\n      buffer.lines.set(2, stringToRow('uvwxyz'));\n\n      selectionService.selectionMode = SelectionMode.COLUMN;\n      selectionService.model.selectionStart = [2, 0];\n      selectionService.model.selectionEnd = [4, 2];\n\n      assert.equal(selectionService.selectionText, 'cd\\nmn\\nwx');\n    });\n\n    it('should select a column of text without chopping up double width characters', () => {\n      buffer.lines.length = 3;\n      buffer.lines.set(0, stringToRow('a'));\n      buffer.lines.set(1, stringToRow('語'));\n      buffer.lines.set(2, stringToRow('b'));\n\n      selectionService.selectionMode = SelectionMode.COLUMN;\n      selectionService.model.selectionStart = [0, 0];\n      selectionService.model.selectionEnd = [1, 2];\n\n      assert.equal(selectionService.selectionText, 'a\\n語\\nb');\n    });\n\n    it('should select a column of text with single character emojis', () => {\n      buffer.lines.length = 3;\n      buffer.lines.set(0, stringToRow('a'));\n      buffer.lines.set(1, stringToRow('☃'));\n      buffer.lines.set(2, stringToRow('c'));\n\n      selectionService.selectionMode = SelectionMode.COLUMN;\n      selectionService.model.selectionStart = [0, 0];\n      selectionService.model.selectionEnd = [1, 2];\n\n      assert.equal(selectionService.selectionText, 'a\\n☃\\nc');\n    });\n\n    it('should select a column of text with double character emojis', () => {\n      // TODO the case this is testing works for me in the demo webapp,\n      // but doing it programmatically fails.\n      buffer.lines.length = 3;\n      buffer.lines.set(0, stringToRow('a '));\n      buffer.lines.set(1, stringArrayToRow(['😁', ' ']));\n      buffer.lines.set(2, stringToRow('c '));\n\n      selectionService.selectionMode = SelectionMode.COLUMN;\n      selectionService.model.selectionStart = [0, 0];\n      selectionService.model.selectionEnd = [1, 2];\n\n      assert.equal(selectionService.selectionText, 'a\\n😁\\nc');\n    });\n  });\n\n  describe('_areCoordsInSelection', () => {\n    it('should return whether coords are in the selection', () => {\n      assert.isFalse(selectionService.areCoordsInSelection([0, 0], [2, 0], [2, 1]));\n      assert.isFalse(selectionService.areCoordsInSelection([1, 0], [2, 0], [2, 1]));\n      assert.isTrue(selectionService.areCoordsInSelection([2, 0], [2, 0], [2, 1]));\n      assert.isTrue(selectionService.areCoordsInSelection([10, 0], [2, 0], [2, 1]));\n      assert.isTrue(selectionService.areCoordsInSelection([0, 1], [2, 0], [2, 1]));\n      assert.isTrue(selectionService.areCoordsInSelection([1, 1], [2, 0], [2, 1]));\n      assert.isFalse(selectionService.areCoordsInSelection([2, 1], [2, 0], [2, 1]));\n    });\n  });\n});\n\n"
  },
  {
    "path": "src/browser/services/SelectionService.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferRange, ILinkifier2 } from 'browser/Types';\nimport { getCoordsRelativeToElement } from 'browser/input/Mouse';\nimport { moveToCellSequence } from 'browser/input/MoveToCell';\nimport { SelectionModel } from 'browser/selection/SelectionModel';\nimport { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';\nimport { ICoreBrowserService, IMouseCoordsService, IRenderService, ISelectionService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport * as Browser from 'common/Platform';\nimport { IBufferLine, ICellData, IDisposable } from 'common/Types';\nimport { getRangeLength } from 'common/buffer/BufferRange';\nimport { CellData } from 'common/buffer/CellData';\nimport { IBuffer } from 'common/buffer/Types';\nimport { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * The maximum amount of time that can have elapsed for an alt click to move the\n * cursor.\n */\nconst ALT_CLICK_MOVE_CURSOR_TIME = 500;\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n  start: number;\n  length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nexport const enum SelectionMode {\n  NORMAL,\n  WORD,\n  LINE,\n  COLUMN\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionService handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionService but the onRedrawRequest event is fired\n * when the selection is ready to be redrawn (on an animation frame).\n */\nexport class SelectionService extends Disposable implements ISelectionService {\n  public serviceBrand: undefined;\n\n  protected _model: SelectionModel;\n\n  /**\n   * The amount to scroll every drag scroll update (depends on how far the mouse\n   * drag is above or below the terminal).\n   */\n  private _dragScrollAmount: number = 0;\n\n  /**\n   * The current selection mode.\n   */\n  protected _activeSelectionMode: SelectionMode;\n\n  /**\n   * A setInterval timer that is active while the mouse is down whose callback\n   * scrolls the viewport when necessary.\n   */\n  private _dragScrollIntervalTimer: number | undefined;\n\n  /**\n   * The animation frame ID used for refreshing the selection.\n   */\n  private _refreshAnimationFrame: number | undefined;\n\n  /**\n   * Whether selection is enabled.\n   */\n  private _enabled = true;\n\n  private _mouseMoveListener: EventListener;\n  private _mouseUpListener: EventListener;\n  private _trimListener: IDisposable;\n  private _workCell: CellData = new CellData();\n\n  private _mouseDownTimeStamp: number = 0;\n  private _oldHasSelection: boolean = false;\n  private _oldSelectionStart: [number, number] | undefined = undefined;\n  private _oldSelectionEnd: [number, number] | undefined = undefined;\n\n  private readonly _onLinuxMouseSelection = this._register(new Emitter<string>());\n  public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event;\n  private readonly _onRedrawRequest = this._register(new Emitter<ISelectionRedrawRequestEvent>());\n  public readonly onRequestRedraw = this._onRedrawRequest.event;\n  private readonly _onSelectionChange = this._register(new Emitter<void>());\n  public readonly onSelectionChange = this._onSelectionChange.event;\n  private readonly _onRequestScrollLines = this._register(new Emitter<ISelectionRequestScrollLinesEvent>());\n  public readonly onRequestScrollLines = this._onRequestScrollLines.event;\n\n  constructor(\n    private readonly _element: HTMLElement,\n    private readonly _screenElement: HTMLElement,\n    private readonly _linkifier: ILinkifier2,\n    @IBufferService private readonly _bufferService: IBufferService,\n    @ICoreService private readonly _coreService: ICoreService,\n    @IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,\n    @IOptionsService private readonly _optionsService: IOptionsService,\n    @IRenderService private readonly _renderService: IRenderService,\n    @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService\n  ) {\n    super();\n\n    // Init listeners\n    this._mouseMoveListener = event => this._handleMouseMove(event as MouseEvent);\n    this._mouseUpListener = event => this._handleMouseUp(event as MouseEvent);\n    this._coreService.onUserInput(() => {\n      if (this.hasSelection) {\n        this.clearSelection();\n      }\n    });\n    this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._handleTrim(amount));\n    this._register(this._bufferService.buffers.onBufferActivate(e => this._handleBufferActivate(e)));\n\n    this.enable();\n\n    this._model = new SelectionModel(this._bufferService);\n    this._activeSelectionMode = SelectionMode.NORMAL;\n\n    this._register(toDisposable(() => {\n      this._removeMouseDownListeners();\n    }));\n\n    // Clear selection when resizing vertically. This experience could be improved, this is the\n    // simple option to fix the buggy behavior. https://github.com/xtermjs/xterm.js/issues/5300\n    this._register(this._bufferService.onResize(e => {\n      if (e.rowsChanged) {\n        this.clearSelection();\n      }\n    }));\n  }\n\n  public reset(): void {\n    this.clearSelection();\n  }\n\n  /**\n   * Disables the selection manager. This is useful for when terminal mouse\n   * are enabled.\n   */\n  public disable(): void {\n    this.clearSelection();\n    this._enabled = false;\n  }\n\n  /**\n   * Enable the selection manager.\n   */\n  public enable(): void {\n    this._enabled = true;\n  }\n\n  public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }\n  public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }\n\n  /**\n   * Gets whether there is an active text selection.\n   */\n  public get hasSelection(): boolean {\n    const start = this._model.finalSelectionStart;\n    const end = this._model.finalSelectionEnd;\n    if (!start || !end) {\n      return false;\n    }\n    return start[0] !== end[0] || start[1] !== end[1];\n  }\n\n  /**\n   * Gets the text currently selected.\n   */\n  public get selectionText(): string {\n    const start = this._model.finalSelectionStart;\n    const end = this._model.finalSelectionEnd;\n    if (!start || !end) {\n      return '';\n    }\n\n    const buffer = this._bufferService.buffer;\n    const result: string[] = [];\n\n    if (this._activeSelectionMode === SelectionMode.COLUMN) {\n      // Ignore zero width selections\n      if (start[0] === end[0]) {\n        return '';\n      }\n\n      // For column selection it's not enough to rely on final selection's swapping of reversed\n      // values, it also needs the x coordinates to swap independently of the y coordinate is needed\n      const startCol = start[0] < end[0] ? start[0] : end[0];\n      const endCol = start[0] < end[0] ? end[0] : start[0];\n      for (let i = start[1]; i <= end[1]; i++) {\n        const lineText = buffer.translateBufferLineToString(i, true, startCol, endCol);\n        result.push(lineText);\n      }\n    } else {\n      // Get first row\n      const startRowEndCol = start[1] === end[1] ? end[0] : undefined;\n      result.push(buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));\n\n      // Get middle rows\n      for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n        const bufferLine = buffer.lines.get(i);\n        const lineText = buffer.translateBufferLineToString(i, true);\n        if (bufferLine?.isWrapped) {\n          result[result.length - 1] += lineText;\n        } else {\n          result.push(lineText);\n        }\n      }\n\n      // Get final row\n      if (start[1] !== end[1]) {\n        const bufferLine = buffer.lines.get(end[1]);\n        const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]);\n        if (bufferLine && bufferLine!.isWrapped) {\n          result[result.length - 1] += lineText;\n        } else {\n          result.push(lineText);\n        }\n      }\n    }\n\n    // Format string by replacing non-breaking space chars with regular spaces\n    // and joining the array into a multi-line string.\n    const formattedResult = result.map(line => {\n      return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n    }).join(Browser.isWindows ? '\\r\\n' : '\\n');\n\n    return formattedResult;\n  }\n\n  /**\n   * Clears the current terminal selection.\n   */\n  public clearSelection(): void {\n    this._model.clearSelection();\n    this._removeMouseDownListeners();\n    this.refresh();\n    this._onSelectionChange.fire();\n  }\n\n  /**\n   * Queues a refresh, redrawing the selection on the next opportunity.\n   * @param isLinuxMouseSelection Whether the selection should be registered as a new\n   * selection on Linux.\n   */\n  public refresh(isLinuxMouseSelection?: boolean): void {\n    // Queue the refresh for the renderer\n    if (!this._refreshAnimationFrame) {\n      this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._refresh());\n    }\n\n    // If the platform is Linux and the refresh call comes from a mouse event,\n    // we need to update the selection for middle click to paste selection.\n    if (Browser.isLinux && isLinuxMouseSelection) {\n      const selectionText = this.selectionText;\n      if (selectionText.length) {\n        this._onLinuxMouseSelection.fire(this.selectionText);\n      }\n    }\n  }\n\n  /**\n   * Fires the refresh event, causing consumers to pick it up and redraw the\n   * selection state.\n   */\n  private _refresh(): void {\n    this._refreshAnimationFrame = undefined;\n    this._onRedrawRequest.fire({\n      start: this._model.finalSelectionStart,\n      end: this._model.finalSelectionEnd,\n      columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN\n    });\n  }\n\n  /**\n   * Checks if the current click was inside the current selection\n   * @param event The mouse event\n   */\n  private _isClickInSelection(event: MouseEvent): boolean {\n    const coords = this._getMouseBufferCoords(event);\n    const start = this._model.finalSelectionStart;\n    const end = this._model.finalSelectionEnd;\n\n    if (!start || !end || !coords) {\n      return false;\n    }\n\n    return this._areCoordsInSelection(coords, start, end);\n  }\n\n  public isCellInSelection(x: number, y: number): boolean {\n    const start = this._model.finalSelectionStart;\n    const end = this._model.finalSelectionEnd;\n    if (!start || !end) {\n      return false;\n    }\n    return this._areCoordsInSelection([x, y], start, end);\n  }\n\n  protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {\n    return (coords[1] > start[1] && coords[1] < end[1]) ||\n        (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||\n        (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) ||\n        (start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]);\n  }\n\n  /**\n   * Selects word at the current mouse event coordinates.\n   * @param event The mouse event.\n   */\n  private _selectWordAtCursor(event: MouseEvent, allowWhitespaceOnlySelection: boolean): boolean {\n    // Check if there is a link under the cursor first and select that if so\n    const range = this._linkifier.currentLink?.link?.range;\n    if (range) {\n      this._model.selectionStart = [range.start.x - 1, range.start.y - 1];\n      this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);\n      this._model.selectionEnd = undefined;\n      return true;\n    }\n\n    const coords = this._getMouseBufferCoords(event);\n    if (coords) {\n      this._selectWordAt(coords, allowWhitespaceOnlySelection);\n      this._model.selectionEnd = undefined;\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Selects all text within the terminal.\n   */\n  public selectAll(): void {\n    this._model.isSelectAllActive = true;\n    this.refresh();\n    this._onSelectionChange.fire();\n  }\n\n  public selectLines(start: number, end: number): void {\n    this._model.clearSelection();\n    start = Math.max(start, 0);\n    end = Math.min(end, this._bufferService.buffer.lines.length - 1);\n    this._model.selectionStart = [0, start];\n    this._model.selectionEnd = [this._bufferService.cols, end];\n    this.refresh();\n    this._onSelectionChange.fire();\n  }\n\n  /**\n   * Handle the buffer being trimmed, adjust the selection position.\n   * @param amount The amount the buffer is being trimmed.\n   */\n  private _handleTrim(amount: number): void {\n    const needsRefresh = this._model.handleTrim(amount);\n    if (needsRefresh) {\n      this.refresh();\n    }\n  }\n\n  /**\n   * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n   * @param event The mouse event.\n   */\n  private _getMouseBufferCoords(event: MouseEvent): [number, number] | undefined {\n    const coords = this._mouseCoordsService.getCoords(event, this._screenElement, this._bufferService.cols, this._bufferService.rows, true);\n    if (!coords) {\n      return undefined;\n    }\n\n    // Convert to 0-based\n    coords[0]--;\n    coords[1]--;\n\n    // Convert viewport coords to buffer coords\n    coords[1] += this._bufferService.buffer.ydisp;\n    return coords;\n  }\n\n  /**\n   * Gets the amount the viewport should be scrolled based on how far out of the\n   * terminal the mouse is.\n   * @param event The mouse event.\n   */\n  private _getMouseEventScrollAmount(event: MouseEvent): number {\n    let offset = getCoordsRelativeToElement(this._coreBrowserService.window, event, this._screenElement)[1];\n    const terminalHeight = this._renderService.dimensions.css.canvas.height;\n    if (offset >= 0 && offset <= terminalHeight) {\n      return 0;\n    }\n    if (offset > terminalHeight) {\n      offset -= terminalHeight;\n    }\n\n    offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n    offset /= DRAG_SCROLL_MAX_THRESHOLD;\n    return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n  }\n\n  /**\n   * Returns whether the selection manager should force selection, regardless of\n   * whether the terminal is in mouse events mode.\n   * @param event The mouse event.\n   */\n  public shouldForceSelection(event: MouseEvent): boolean {\n    if (Browser.isMac) {\n      return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection;\n    }\n\n    return event.shiftKey;\n  }\n\n  /**\n   * Handles te mousedown event, setting up for a new selection.\n   * @param event The mousedown event.\n   */\n  public handleMouseDown(event: MouseEvent): void {\n    this._mouseDownTimeStamp = event.timeStamp;\n    // If we have selection, we want the context menu on right click even if the\n    // terminal is in mouse mode.\n    if (event.button === 2 && this.hasSelection) {\n      return;\n    }\n\n    // Only action the primary button\n    if (event.button !== 0) {\n      return;\n    }\n\n    // Allow selection when using a specific modifier key, even when disabled\n    if (!this._enabled) {\n      if (!this.shouldForceSelection(event)) {\n        return;\n      }\n\n      // Don't send the mouse down event to the current process, we want to select\n      event.stopPropagation();\n    }\n\n    // Tell the browser not to start a regular selection\n    event.preventDefault();\n\n    // Reset drag scroll state\n    this._dragScrollAmount = 0;\n\n    if (this._enabled && event.shiftKey) {\n      this._handleIncrementalClick(event);\n    } else {\n      if (event.detail === 1) {\n        this._handleSingleClick(event);\n      } else if (event.detail === 2) {\n        this._handleDoubleClick(event);\n      } else if (event.detail === 3) {\n        this._handleTripleClick(event);\n      }\n    }\n\n    this._addMouseDownListeners();\n    this.refresh(true);\n  }\n\n  /**\n   * Adds listeners when mousedown is triggered.\n   */\n  private _addMouseDownListeners(): void {\n    // Listen on the document so that dragging outside of viewport works\n    if (this._screenElement.ownerDocument) {\n      this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n      this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n    }\n    this._dragScrollIntervalTimer = this._coreBrowserService.window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n  }\n\n  /**\n   * Removes the listeners that are registered when mousedown is triggered.\n   */\n  private _removeMouseDownListeners(): void {\n    if (this._screenElement.ownerDocument) {\n      this._screenElement.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n      this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n    }\n    this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer);\n    this._dragScrollIntervalTimer = undefined;\n  }\n\n  /**\n   * Performs an incremental click, setting the selection end position to the mouse\n   * position.\n   * @param event The mouse event.\n   */\n  private _handleIncrementalClick(event: MouseEvent): void {\n    if (this._model.selectionStart) {\n      this._model.selectionEnd = this._getMouseBufferCoords(event);\n    }\n  }\n\n  /**\n   * Performs a single click, resetting relevant state and setting the selection\n   * start position.\n   * @param event The mouse event.\n   */\n  private _handleSingleClick(event: MouseEvent): void {\n    // Track if there was a selection before clearing\n    const hadSelection = this.hasSelection;\n\n    this._model.selectionStartLength = 0;\n    this._model.isSelectAllActive = false;\n    this._activeSelectionMode = this.shouldColumnSelect(event) ? SelectionMode.COLUMN : SelectionMode.NORMAL;\n\n    // Initialize the new selection\n    this._model.selectionStart = this._getMouseBufferCoords(event);\n    if (!this._model.selectionStart) {\n      return;\n    }\n    this._model.selectionEnd = undefined;\n\n    // Fire selection change event if a selection was cleared\n    if (hadSelection) {\n      this._fireOnSelectionChange(this._model.finalSelectionStart, this._model.finalSelectionEnd, false);\n    }\n\n    // Ensure the line exists\n    const line = this._bufferService.buffer.lines.get(this._model.selectionStart[1]);\n    if (!line) {\n      return;\n    }\n\n    // Return early if the click event is not in the buffer (eg. in scroll bar)\n    if (line.length === this._model.selectionStart[0]) {\n      return;\n    }\n\n    // If the mouse is over the second half of a wide character, adjust the\n    // selection to cover the whole character\n    if (line.hasWidth(this._model.selectionStart[0]) === 0) {\n      this._model.selectionStart[0]++;\n    }\n  }\n\n  /**\n   * Performs a double click, selecting the current word.\n   * @param event The mouse event.\n   */\n  private _handleDoubleClick(event: MouseEvent): void {\n    if (this._selectWordAtCursor(event, true)) {\n      this._activeSelectionMode = SelectionMode.WORD;\n    }\n  }\n\n  /**\n   * Performs a triple click, selecting the current line and activating line\n   * select mode.\n   * @param event The mouse event.\n   */\n  private _handleTripleClick(event: MouseEvent): void {\n    const coords = this._getMouseBufferCoords(event);\n    if (coords) {\n      this._activeSelectionMode = SelectionMode.LINE;\n      this._selectLineAt(coords[1]);\n    }\n  }\n\n  /**\n   * Returns whether the selection manager should operate in column select mode\n   * @param event the mouse or keyboard event\n   */\n  public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {\n    return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection);\n  }\n\n  /**\n   * Handles the mousemove event when the mouse button is down, recording the\n   * end of the selection and refreshing the selection.\n   * @param event The mousemove event.\n   */\n  private _handleMouseMove(event: MouseEvent): void {\n    // If the mousemove listener is active it means that a selection is\n    // currently being made, we should stop propagation to prevent mouse events\n    // to be sent to the pty.\n    event.stopImmediatePropagation();\n\n    // Do nothing if there is no selection start, this can happen if the first\n    // click in the terminal is an incremental click\n    if (!this._model.selectionStart) {\n      return;\n    }\n\n    // Record the previous position so we know whether to redraw the selection\n    // at the end.\n    const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n    // Set the initial selection end based on the mouse coordinates\n    this._model.selectionEnd = this._getMouseBufferCoords(event);\n    if (!this._model.selectionEnd) {\n      this.refresh(true);\n      return;\n    }\n\n    // Select the entire line if line select mode is active.\n    if (this._activeSelectionMode === SelectionMode.LINE) {\n      if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n        this._model.selectionEnd[0] = 0;\n      } else {\n        this._model.selectionEnd[0] = this._bufferService.cols;\n      }\n    } else if (this._activeSelectionMode === SelectionMode.WORD) {\n      this._selectToWordAt(this._model.selectionEnd);\n    }\n\n    // Determine the amount of scrolling that will happen.\n    this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n    // If the cursor was above or below the viewport, make sure it's at the\n    // start or end of the viewport respectively. This should only happen when\n    // NOT in column select mode.\n    if (this._activeSelectionMode !== SelectionMode.COLUMN) {\n      if (this._dragScrollAmount > 0) {\n        this._model.selectionEnd[0] = this._bufferService.cols;\n      } else if (this._dragScrollAmount < 0) {\n        this._model.selectionEnd[0] = 0;\n      }\n    }\n\n    // If the character is a wide character include the cell to the right in the\n    // selection. Note that selections at the very end of the line will never\n    // have a character.\n    const buffer = this._bufferService.buffer;\n    if (this._model.selectionEnd[1] < buffer.lines.length) {\n      const line = buffer.lines.get(this._model.selectionEnd[1]);\n      if (line && line.hasWidth(this._model.selectionEnd[0]) === 0) {\n        if (this._model.selectionEnd[0] < this._bufferService.cols) {\n          this._model.selectionEnd[0]++;\n        }\n      }\n    }\n\n    // Only draw here if the selection changes.\n    if (!previousSelectionEnd ||\n      previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n      previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n      this.refresh(true);\n    }\n  }\n\n  /**\n   * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n   * scrolling of the viewport.\n   */\n  private _dragScroll(): void {\n    if (!this._model.selectionEnd || !this._model.selectionStart) {\n      return;\n    }\n    if (this._dragScrollAmount) {\n      this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });\n      // Re-evaluate selection\n      // If the cursor was above or below the viewport, make sure it's at the\n      // start or end of the viewport respectively. This should only happen when\n      // NOT in column select mode.\n      const buffer = this._bufferService.buffer;\n      if (this._dragScrollAmount > 0) {\n        if (this._activeSelectionMode !== SelectionMode.COLUMN) {\n          this._model.selectionEnd[0] = this._bufferService.cols;\n        }\n        this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1);\n      } else {\n        if (this._activeSelectionMode !== SelectionMode.COLUMN) {\n          this._model.selectionEnd[0] = 0;\n        }\n        this._model.selectionEnd[1] = buffer.ydisp;\n      }\n      this.refresh();\n    }\n  }\n\n  /**\n   * Handles the mouseup event, removing the mousedown listeners.\n   * @param event The mouseup event.\n   */\n  private _handleMouseUp(event: MouseEvent): void {\n    const timeElapsed = event.timeStamp - this._mouseDownTimeStamp;\n\n    this._removeMouseDownListeners();\n\n    if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.rawOptions.altClickMovesCursor) {\n      if (this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) {\n        const coordinates = this._mouseCoordsService.getCoords(\n          event,\n          this._element,\n          this._bufferService.cols,\n          this._bufferService.rows,\n          false\n        );\n        if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) {\n          const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._coreService.decPrivateModes.applicationCursorKeys);\n          this._coreService.triggerDataEvent(sequence, true);\n        }\n      }\n    } else {\n      this._fireEventIfSelectionChanged();\n    }\n  }\n\n  private _fireEventIfSelectionChanged(): void {\n    const start = this._model.finalSelectionStart;\n    const end = this._model.finalSelectionEnd;\n    const hasSelection = !!start && !!end && (start[0] !== end[0] || start[1] !== end[1]);\n\n    if (!hasSelection) {\n      if (this._oldHasSelection) {\n        this._fireOnSelectionChange(start, end, hasSelection);\n      }\n      return;\n    }\n\n    // Sanity check, these should not be undefined as there is a selection\n    if (!start || !end) {\n      return;\n    }\n\n    if (!this._oldSelectionStart || !this._oldSelectionEnd || (\n      start[0] !== this._oldSelectionStart[0] || start[1] !== this._oldSelectionStart[1] ||\n      end[0] !== this._oldSelectionEnd[0] || end[1] !== this._oldSelectionEnd[1])) {\n\n      this._fireOnSelectionChange(start, end, hasSelection);\n    }\n  }\n\n  private _fireOnSelectionChange(start: [number, number] | undefined, end: [number, number] | undefined, hasSelection: boolean): void {\n    this._oldSelectionStart = start;\n    this._oldSelectionEnd = end;\n    this._oldHasSelection = hasSelection;\n    this._onSelectionChange.fire();\n  }\n\n  private _handleBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {\n    this.clearSelection();\n    // Only adjust the selection on trim, shiftElements is rarely used (only in\n    // reverseIndex) and delete in a splice is only ever used when the same\n    // number of elements was just added. Given this is could actually be\n    // beneficial to leave the selection as is for these cases.\n    this._trimListener.dispose();\n    this._trimListener = e.activeBuffer.lines.onTrim(amount => this._handleTrim(amount));\n  }\n\n  /**\n   * Converts a viewport column (0 to cols - 1) to the character index on the\n   * buffer line, the latter takes into account wide and null characters.\n   * @param bufferLine The buffer line to use.\n   * @param x The x index in the buffer line to convert.\n   */\n  private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, x: number): number {\n    let charIndex = x;\n    for (let i = 0; x >= i; i++) {\n      const length = bufferLine.loadCell(i, this._workCell).getChars().length;\n      if (this._workCell.getWidth() === 0) {\n        // Wide characters aren't included in the line string so decrement the\n        // index so the index is back on the wide character.\n        charIndex--;\n      } else if (length > 1 && x !== i) {\n        // Emojis take up multiple characters, so adjust accordingly. For these\n        // we don't want ot include the character at the column as we're\n        // returning the start index in the string, not the end index.\n        charIndex += length - 1;\n      }\n    }\n    return charIndex;\n  }\n\n  public setSelection(col: number, row: number, length: number): void {\n    this._model.clearSelection();\n    this._removeMouseDownListeners();\n    this._model.selectionStart = [col, row];\n    this._model.selectionStartLength = length;\n    this.refresh();\n    this._fireEventIfSelectionChanged();\n  }\n\n  public rightClickSelect(ev: MouseEvent): void {\n    if (!this._isClickInSelection(ev)) {\n      if (this._selectWordAtCursor(ev, false)) {\n        this.refresh(true);\n      }\n      this._fireEventIfSelectionChanged();\n    }\n  }\n\n  /**\n   * Gets positional information for the word at the coordinated specified.\n   * @param coords The coordinates to get the word at.\n   */\n  private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition | undefined {\n    // Ensure coords are within viewport (eg. not within scroll bar)\n    if (coords[0] >= this._bufferService.cols) {\n      return undefined;\n    }\n\n    const buffer = this._bufferService.buffer;\n    const bufferLine = buffer.lines.get(coords[1]);\n    if (!bufferLine) {\n      return undefined;\n    }\n\n    const line = buffer.translateBufferLineToString(coords[1], false);\n\n    // Get actual index, taking into consideration wide characters\n    let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords[0]);\n    let endIndex = startIndex;\n\n    // Record offset to be used later\n    const charOffset = coords[0] - startIndex;\n    let leftWideCharCount = 0;\n    let rightWideCharCount = 0;\n    let leftLongCharOffset = 0;\n    let rightLongCharOffset = 0;\n\n    if (line.charAt(startIndex) === ' ') {\n      // Expand until non-whitespace is hit\n      while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n        startIndex--;\n      }\n      while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n        endIndex++;\n      }\n    } else {\n      // Expand until whitespace is hit. This algorithm works by scanning left\n      // and right from the starting position, keeping both the index format\n      // (line) and the column format (bufferLine) in sync. When a wide\n      // character is hit, it is recorded and the column index is adjusted.\n      let startCol = coords[0];\n      let endCol = coords[0];\n\n      // Consider the initial position, skip it and increment the wide char\n      // variable\n      if (bufferLine.getWidth(startCol) === 0) {\n        leftWideCharCount++;\n        startCol--;\n      }\n      if (bufferLine.getWidth(endCol) === 2) {\n        rightWideCharCount++;\n        endCol++;\n      }\n\n      // Adjust the end index for characters whose length are > 1 (emojis)\n      const length = bufferLine.getString(endCol).length;\n      if (length > 1) {\n        rightLongCharOffset += length - 1;\n        endIndex += length - 1;\n      }\n\n      // Expand the string in both directions until a space is hit\n      while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) {\n        bufferLine.loadCell(startCol - 1, this._workCell);\n        const length = this._workCell.getChars().length;\n        if (this._workCell.getWidth() === 0) {\n          // If the next character is a wide char, record it and skip the column\n          leftWideCharCount++;\n          startCol--;\n        } else if (length > 1) {\n          // If the next character's string is longer than 1 char (eg. emoji),\n          // adjust the index\n          leftLongCharOffset += length - 1;\n          startIndex -= length - 1;\n        }\n        startIndex--;\n        startCol--;\n      }\n      while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) {\n        bufferLine.loadCell(endCol + 1, this._workCell);\n        const length = this._workCell.getChars().length;\n        if (this._workCell.getWidth() === 2) {\n          // If the next character is a wide char, record it and skip the column\n          rightWideCharCount++;\n          endCol++;\n        } else if (length > 1) {\n          // If the next character's string is longer than 1 char (eg. emoji),\n          // adjust the index\n          rightLongCharOffset += length - 1;\n          endIndex += length - 1;\n        }\n        endIndex++;\n        endCol++;\n      }\n    }\n\n    // Incremenet the end index so it is at the start of the next character\n    endIndex++;\n\n    // Calculate the start _column_, converting the the string indexes back to\n    // column coordinates.\n    let start =\n        startIndex // The index of the selection's start char in the line string\n        + charOffset // The difference between the initial char's column and index\n        - leftWideCharCount // The number of wide chars left of the initial char\n        + leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)\n\n    // Calculate the length in _columns_, converting the the string indexes back\n    // to column coordinates.\n    let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols\n      endIndex // The index of the selection's end char in the line string\n      - startIndex // The index of the selection's start char in the line string\n      + leftWideCharCount // The number of wide chars left of the initial char\n      + rightWideCharCount // The number of wide chars right of the initial char (inclusive)\n      - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)\n      - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)\n\n    if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {\n      return undefined;\n    }\n\n    // Recurse upwards if the line is wrapped and the word wraps to the above line\n    if (followWrappedLinesAbove) {\n      if (start === 0 && bufferLine.getCodePoint(0) !== 32 /* ' ' */) {\n        const previousBufferLine = buffer.lines.get(coords[1] - 1);\n        if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {\n          const previousLineWordPosition = this._getWordAt([this._bufferService.cols - 1, coords[1] - 1], false, true, false);\n          if (previousLineWordPosition) {\n            const offset = this._bufferService.cols - previousLineWordPosition.start;\n            start -= offset;\n            length += offset;\n          }\n        }\n      }\n    }\n\n    // Recurse downwards if the line is wrapped and the word wraps to the next line\n    if (followWrappedLinesBelow) {\n      if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {\n        const nextBufferLine = buffer.lines.get(coords[1] + 1);\n        if (nextBufferLine?.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /* ' ' */) {\n          const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);\n          if (nextLineWordPosition) {\n            length += nextLineWordPosition.length;\n          }\n        }\n      }\n    }\n\n    return { start, length };\n  }\n\n  /**\n   * Selects the word at the coordinates specified.\n   * @param coords The coordinates to get the word at.\n   * @param allowWhitespaceOnlySelection If whitespace should be selected\n   */\n  protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {\n    const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);\n    if (wordPosition) {\n      // Adjust negative start value\n      while (wordPosition.start < 0) {\n        wordPosition.start += this._bufferService.cols;\n        coords[1]--;\n      }\n      this._model.selectionStart = [wordPosition.start, coords[1]];\n      this._model.selectionStartLength = wordPosition.length;\n    }\n  }\n\n  /**\n   * Sets the selection end to the word at the coordinated specified.\n   * @param coords The coordinates to get the word at.\n   */\n  private _selectToWordAt(coords: [number, number]): void {\n    const wordPosition = this._getWordAt(coords, true);\n    if (wordPosition) {\n      let endRow = coords[1];\n\n      // Adjust negative start value\n      while (wordPosition.start < 0) {\n        wordPosition.start += this._bufferService.cols;\n        endRow--;\n      }\n\n      // Adjust wrapped length value, this only needs to happen when values are reversed as in that\n      // case we're interested in the start of the word, not the end\n      if (!this._model.areSelectionValuesReversed()) {\n        while (wordPosition.start + wordPosition.length > this._bufferService.cols) {\n          wordPosition.length -= this._bufferService.cols;\n          endRow++;\n        }\n      }\n\n      this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow];\n    }\n  }\n\n  /**\n   * Gets whether the character is considered a word separator by the select\n   * word logic.\n   * @param cell The cell to check.\n   */\n  private _isCharWordSeparator(cell: ICellData): boolean {\n    // Zero width characters are never separators as they are always to the\n    // right of wide characters\n    if (cell.getWidth() === 0) {\n      return false;\n    }\n    return this._optionsService.rawOptions.wordSeparator.indexOf(cell.getChars()) >= 0;\n  }\n\n  /**\n   * Selects the line specified.\n   * @param line The line index.\n   */\n  protected _selectLineAt(line: number): void {\n    const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line);\n    const range: IBufferRange = {\n      start: { x: 0, y: wrappedRange.first },\n      end: { x: this._bufferService.cols - 1, y: wrappedRange.last }\n    };\n    this._model.selectionStart = [0, wrappedRange.first];\n    this._model.selectionEnd = undefined;\n    this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);\n  }\n}\n"
  },
  {
    "path": "src/browser/services/Services.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';\nimport { IColorSet, ILink, ReadonlyColorSet } from 'browser/Types';\nimport { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';\nimport { createDecorator } from 'common/services/ServiceRegistry';\nimport { AllColorIndex, IDisposable, IKeyboardResult } from 'common/Types';\nimport type { IEvent } from 'common/Event';\n\nexport const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');\nexport interface ICharSizeService {\n  serviceBrand: undefined;\n\n  readonly width: number;\n  readonly height: number;\n  readonly hasValidSize: boolean;\n\n  readonly onCharSizeChange: IEvent<void>;\n\n  measure(): void;\n}\n\nexport const ICoreBrowserService = createDecorator<ICoreBrowserService>('CoreBrowserService');\nexport interface ICoreBrowserService {\n  serviceBrand: undefined;\n\n  readonly isFocused: boolean;\n\n  readonly onDprChange: IEvent<number>;\n  readonly onWindowChange: IEvent<Window & typeof globalThis>;\n\n  /**\n   * Gets or sets the parent window that the terminal is rendered into. DOM and rendering APIs (e.g.\n   * requestAnimationFrame) should be invoked in the context of this window. This should be set when\n   * the window hosting the xterm.js instance changes.\n   */\n  window: Window & typeof globalThis;\n  /**\n   * The document of the primary window to be used to create elements when working with multiple\n   * windows. This is defined by the documentOverride setting.\n   */\n  readonly mainDocument: Document;\n  /**\n   * Helper for getting the devicePixelRatio of the parent window.\n   */\n  readonly dpr: number;\n}\n\nexport const IMouseCoordsService = createDecorator<IMouseCoordsService>('MouseCoordsService');\nexport interface IMouseCoordsService {\n  serviceBrand: undefined;\n\n  getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;\n  getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined;\n}\n\nexport const IMouseService = createDecorator<IMouseService>('MouseService');\nexport interface IMouseService {\n  serviceBrand: undefined;\n\n  bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void;\n  reset(): void;\n}\nexport interface IMouseServiceTarget {\n  element: HTMLElement;\n  screenElement: HTMLElement;\n  document: Document;\n  handleTouchScroll?(amount: number): void;\n}\n\nexport const IRenderService = createDecorator<IRenderService>('RenderService');\nexport interface IRenderService extends IDisposable {\n  serviceBrand: undefined;\n\n  onDimensionsChange: IEvent<IRenderDimensions>;\n  /**\n   * Fires when buffer changes are rendered. This does not fire when only cursor\n   * or selections are rendered.\n   */\n  onRenderedViewportChange: IEvent<{ start: number, end: number }>;\n  /**\n   * Fires on render\n   */\n  onRender: IEvent<{ start: number, end: number }>;\n  onRefreshRequest: IEvent<{ start: number, end: number }>;\n\n  dimensions: IRenderDimensions;\n\n  addRefreshCallback(callback: FrameRequestCallback): number;\n\n  refreshRows(start: number, end: number, sync?: boolean): void;\n  clearTextureAtlas(): void;\n  resize(cols: number, rows: number): void;\n  hasRenderer(): boolean;\n  setRenderer(renderer: IRenderer): void;\n  handleDevicePixelRatioChange(): void;\n  handleResize(cols: number, rows: number): void;\n  handleCharSizeChanged(): void;\n  handleBlur(): void;\n  handleFocus(): void;\n  handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;\n  handleCursorMove(): void;\n  clear(): void;\n}\n\nexport const ISelectionService = createDecorator<ISelectionService>('SelectionService');\nexport interface ISelectionService {\n  serviceBrand: undefined;\n\n  readonly selectionText: string;\n  readonly hasSelection: boolean;\n  readonly selectionStart: [number, number] | undefined;\n  readonly selectionEnd: [number, number] | undefined;\n\n  readonly onLinuxMouseSelection: IEvent<string>;\n  readonly onRequestRedraw: IEvent<ISelectionRequestRedrawEvent>;\n  readonly onRequestScrollLines: IEvent<ISelectionRequestScrollLinesEvent>;\n  readonly onSelectionChange: IEvent<void>;\n\n  disable(): void;\n  enable(): void;\n  reset(): void;\n  setSelection(row: number, col: number, length: number): void;\n  selectAll(): void;\n  selectLines(start: number, end: number): void;\n  clearSelection(): void;\n  rightClickSelect(event: MouseEvent): void;\n  shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean;\n  shouldForceSelection(event: MouseEvent): boolean;\n  refresh(isLinuxMouseSelection?: boolean): void;\n  handleMouseDown(event: MouseEvent): void;\n  isCellInSelection(x: number, y: number): boolean;\n}\n\nexport const ICharacterJoinerService = createDecorator<ICharacterJoinerService>('CharacterJoinerService');\nexport interface ICharacterJoinerService {\n  serviceBrand: undefined;\n\n  register(handler: (text: string) => [number, number][]): number;\n  deregister(joinerId: number): boolean;\n  getJoinedCharacters(row: number): [number, number][];\n}\n\nexport const IThemeService = createDecorator<IThemeService>('ThemeService');\nexport interface IThemeService {\n  serviceBrand: undefined;\n\n  readonly colors: ReadonlyColorSet;\n\n  readonly onChangeColors: IEvent<ReadonlyColorSet>;\n\n  restoreColor(slot?: AllColorIndex): void;\n  /**\n   * Allows external modifying of colors in the theme, this is used instead of {@link colors} to\n   * prevent accidental writes.\n   */\n  modifyColors(callback: (colors: IColorSet) => void): void;\n}\n\n\nexport const ILinkProviderService = createDecorator<ILinkProviderService>('LinkProviderService');\nexport interface ILinkProviderService extends IDisposable {\n  serviceBrand: undefined;\n  readonly linkProviders: ReadonlyArray<ILinkProvider>;\n  registerLinkProvider(linkProvider: ILinkProvider): IDisposable;\n}\nexport interface ILinkProvider {\n  provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void;\n}\n\nexport const IKeyboardService = createDecorator<IKeyboardService>('KeyboardService');\nexport interface IKeyboardService {\n  serviceBrand: undefined;\n  evaluateKeyDown(event: KeyboardEvent): IKeyboardResult;\n  evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined;\n  readonly useKitty: boolean;\n  readonly useWin32InputMode: boolean;\n}\n"
  },
  {
    "path": "src/browser/services/ThemeService.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport jsdom = require('jsdom');\nimport { assert } from 'chai';\nimport { ThemeService } from 'browser/services/ThemeService';\nimport { OptionsService } from 'common/services/OptionsService';\nimport { DEFAULT_ANSI_COLORS } from 'browser/Types';\n\ndescribe('ThemeService', () => {\n  let themeService: ThemeService;\n  let dom: jsdom.JSDOM;\n  let window: jsdom.DOMWindow;\n  let optionsService: OptionsService;\n\n  beforeEach(() => {\n    dom = new jsdom.JSDOM('');\n    window = dom.window;\n    (window as any).HTMLCanvasElement.prototype.getContext = () => ({\n      createLinearGradient(): any {\n        return null;\n      },\n\n      fillRect(): void { },\n\n      getImageData(): any {\n        return {data: [0, 0, 0, 0xFF]};\n      }\n    });\n    optionsService = new OptionsService({});\n    themeService = new ThemeService(optionsService);\n  });\n\n  describe('constructor', () => {\n    it('should fill all colors with values', () => {\n      for (const key of Object.keys(themeService.colors)) {\n        if (![\n          'ansi',\n          'contrastCache',\n          'halfContrastCache',\n          'selectionForeground'\n        ].includes(key)) {\n          // A #rrggbb or rgba(...)\n          assert.ok((themeService.colors as any)[key].css.length >= 7);\n        }\n      }\n      assert.equal(themeService.colors.ansi.length, 256);\n    });\n\n    it('should fill 240 colors with expected values', () => {\n      assert.equal(themeService.colors.ansi[16].css, '#000000');\n      assert.equal(themeService.colors.ansi[17].css, '#00005f');\n      assert.equal(themeService.colors.ansi[18].css, '#000087');\n      assert.equal(themeService.colors.ansi[19].css, '#0000af');\n      assert.equal(themeService.colors.ansi[20].css, '#0000d7');\n      assert.equal(themeService.colors.ansi[21].css, '#0000ff');\n      assert.equal(themeService.colors.ansi[22].css, '#005f00');\n      assert.equal(themeService.colors.ansi[23].css, '#005f5f');\n      assert.equal(themeService.colors.ansi[24].css, '#005f87');\n      assert.equal(themeService.colors.ansi[25].css, '#005faf');\n      assert.equal(themeService.colors.ansi[26].css, '#005fd7');\n      assert.equal(themeService.colors.ansi[27].css, '#005fff');\n      assert.equal(themeService.colors.ansi[28].css, '#008700');\n      assert.equal(themeService.colors.ansi[29].css, '#00875f');\n      assert.equal(themeService.colors.ansi[30].css, '#008787');\n      assert.equal(themeService.colors.ansi[31].css, '#0087af');\n      assert.equal(themeService.colors.ansi[32].css, '#0087d7');\n      assert.equal(themeService.colors.ansi[33].css, '#0087ff');\n      assert.equal(themeService.colors.ansi[34].css, '#00af00');\n      assert.equal(themeService.colors.ansi[35].css, '#00af5f');\n      assert.equal(themeService.colors.ansi[36].css, '#00af87');\n      assert.equal(themeService.colors.ansi[37].css, '#00afaf');\n      assert.equal(themeService.colors.ansi[38].css, '#00afd7');\n      assert.equal(themeService.colors.ansi[39].css, '#00afff');\n      assert.equal(themeService.colors.ansi[40].css, '#00d700');\n      assert.equal(themeService.colors.ansi[41].css, '#00d75f');\n      assert.equal(themeService.colors.ansi[42].css, '#00d787');\n      assert.equal(themeService.colors.ansi[43].css, '#00d7af');\n      assert.equal(themeService.colors.ansi[44].css, '#00d7d7');\n      assert.equal(themeService.colors.ansi[45].css, '#00d7ff');\n      assert.equal(themeService.colors.ansi[46].css, '#00ff00');\n      assert.equal(themeService.colors.ansi[47].css, '#00ff5f');\n      assert.equal(themeService.colors.ansi[48].css, '#00ff87');\n      assert.equal(themeService.colors.ansi[49].css, '#00ffaf');\n      assert.equal(themeService.colors.ansi[50].css, '#00ffd7');\n      assert.equal(themeService.colors.ansi[51].css, '#00ffff');\n      assert.equal(themeService.colors.ansi[52].css, '#5f0000');\n      assert.equal(themeService.colors.ansi[53].css, '#5f005f');\n      assert.equal(themeService.colors.ansi[54].css, '#5f0087');\n      assert.equal(themeService.colors.ansi[55].css, '#5f00af');\n      assert.equal(themeService.colors.ansi[56].css, '#5f00d7');\n      assert.equal(themeService.colors.ansi[57].css, '#5f00ff');\n      assert.equal(themeService.colors.ansi[58].css, '#5f5f00');\n      assert.equal(themeService.colors.ansi[59].css, '#5f5f5f');\n      assert.equal(themeService.colors.ansi[60].css, '#5f5f87');\n      assert.equal(themeService.colors.ansi[61].css, '#5f5faf');\n      assert.equal(themeService.colors.ansi[62].css, '#5f5fd7');\n      assert.equal(themeService.colors.ansi[63].css, '#5f5fff');\n      assert.equal(themeService.colors.ansi[64].css, '#5f8700');\n      assert.equal(themeService.colors.ansi[65].css, '#5f875f');\n      assert.equal(themeService.colors.ansi[66].css, '#5f8787');\n      assert.equal(themeService.colors.ansi[67].css, '#5f87af');\n      assert.equal(themeService.colors.ansi[68].css, '#5f87d7');\n      assert.equal(themeService.colors.ansi[69].css, '#5f87ff');\n      assert.equal(themeService.colors.ansi[70].css, '#5faf00');\n      assert.equal(themeService.colors.ansi[71].css, '#5faf5f');\n      assert.equal(themeService.colors.ansi[72].css, '#5faf87');\n      assert.equal(themeService.colors.ansi[73].css, '#5fafaf');\n      assert.equal(themeService.colors.ansi[74].css, '#5fafd7');\n      assert.equal(themeService.colors.ansi[75].css, '#5fafff');\n      assert.equal(themeService.colors.ansi[76].css, '#5fd700');\n      assert.equal(themeService.colors.ansi[77].css, '#5fd75f');\n      assert.equal(themeService.colors.ansi[78].css, '#5fd787');\n      assert.equal(themeService.colors.ansi[79].css, '#5fd7af');\n      assert.equal(themeService.colors.ansi[80].css, '#5fd7d7');\n      assert.equal(themeService.colors.ansi[81].css, '#5fd7ff');\n      assert.equal(themeService.colors.ansi[82].css, '#5fff00');\n      assert.equal(themeService.colors.ansi[83].css, '#5fff5f');\n      assert.equal(themeService.colors.ansi[84].css, '#5fff87');\n      assert.equal(themeService.colors.ansi[85].css, '#5fffaf');\n      assert.equal(themeService.colors.ansi[86].css, '#5fffd7');\n      assert.equal(themeService.colors.ansi[87].css, '#5fffff');\n      assert.equal(themeService.colors.ansi[88].css, '#870000');\n      assert.equal(themeService.colors.ansi[89].css, '#87005f');\n      assert.equal(themeService.colors.ansi[90].css, '#870087');\n      assert.equal(themeService.colors.ansi[91].css, '#8700af');\n      assert.equal(themeService.colors.ansi[92].css, '#8700d7');\n      assert.equal(themeService.colors.ansi[93].css, '#8700ff');\n      assert.equal(themeService.colors.ansi[94].css, '#875f00');\n      assert.equal(themeService.colors.ansi[95].css, '#875f5f');\n      assert.equal(themeService.colors.ansi[96].css, '#875f87');\n      assert.equal(themeService.colors.ansi[97].css, '#875faf');\n      assert.equal(themeService.colors.ansi[98].css, '#875fd7');\n      assert.equal(themeService.colors.ansi[99].css, '#875fff');\n      assert.equal(themeService.colors.ansi[100].css, '#878700');\n      assert.equal(themeService.colors.ansi[101].css, '#87875f');\n      assert.equal(themeService.colors.ansi[102].css, '#878787');\n      assert.equal(themeService.colors.ansi[103].css, '#8787af');\n      assert.equal(themeService.colors.ansi[104].css, '#8787d7');\n      assert.equal(themeService.colors.ansi[105].css, '#8787ff');\n      assert.equal(themeService.colors.ansi[106].css, '#87af00');\n      assert.equal(themeService.colors.ansi[107].css, '#87af5f');\n      assert.equal(themeService.colors.ansi[108].css, '#87af87');\n      assert.equal(themeService.colors.ansi[109].css, '#87afaf');\n      assert.equal(themeService.colors.ansi[110].css, '#87afd7');\n      assert.equal(themeService.colors.ansi[111].css, '#87afff');\n      assert.equal(themeService.colors.ansi[112].css, '#87d700');\n      assert.equal(themeService.colors.ansi[113].css, '#87d75f');\n      assert.equal(themeService.colors.ansi[114].css, '#87d787');\n      assert.equal(themeService.colors.ansi[115].css, '#87d7af');\n      assert.equal(themeService.colors.ansi[116].css, '#87d7d7');\n      assert.equal(themeService.colors.ansi[117].css, '#87d7ff');\n      assert.equal(themeService.colors.ansi[118].css, '#87ff00');\n      assert.equal(themeService.colors.ansi[119].css, '#87ff5f');\n      assert.equal(themeService.colors.ansi[120].css, '#87ff87');\n      assert.equal(themeService.colors.ansi[121].css, '#87ffaf');\n      assert.equal(themeService.colors.ansi[122].css, '#87ffd7');\n      assert.equal(themeService.colors.ansi[123].css, '#87ffff');\n      assert.equal(themeService.colors.ansi[124].css, '#af0000');\n      assert.equal(themeService.colors.ansi[125].css, '#af005f');\n      assert.equal(themeService.colors.ansi[126].css, '#af0087');\n      assert.equal(themeService.colors.ansi[127].css, '#af00af');\n      assert.equal(themeService.colors.ansi[128].css, '#af00d7');\n      assert.equal(themeService.colors.ansi[129].css, '#af00ff');\n      assert.equal(themeService.colors.ansi[130].css, '#af5f00');\n      assert.equal(themeService.colors.ansi[131].css, '#af5f5f');\n      assert.equal(themeService.colors.ansi[132].css, '#af5f87');\n      assert.equal(themeService.colors.ansi[133].css, '#af5faf');\n      assert.equal(themeService.colors.ansi[134].css, '#af5fd7');\n      assert.equal(themeService.colors.ansi[135].css, '#af5fff');\n      assert.equal(themeService.colors.ansi[136].css, '#af8700');\n      assert.equal(themeService.colors.ansi[137].css, '#af875f');\n      assert.equal(themeService.colors.ansi[138].css, '#af8787');\n      assert.equal(themeService.colors.ansi[139].css, '#af87af');\n      assert.equal(themeService.colors.ansi[140].css, '#af87d7');\n      assert.equal(themeService.colors.ansi[141].css, '#af87ff');\n      assert.equal(themeService.colors.ansi[142].css, '#afaf00');\n      assert.equal(themeService.colors.ansi[143].css, '#afaf5f');\n      assert.equal(themeService.colors.ansi[144].css, '#afaf87');\n      assert.equal(themeService.colors.ansi[145].css, '#afafaf');\n      assert.equal(themeService.colors.ansi[146].css, '#afafd7');\n      assert.equal(themeService.colors.ansi[147].css, '#afafff');\n      assert.equal(themeService.colors.ansi[148].css, '#afd700');\n      assert.equal(themeService.colors.ansi[149].css, '#afd75f');\n      assert.equal(themeService.colors.ansi[150].css, '#afd787');\n      assert.equal(themeService.colors.ansi[151].css, '#afd7af');\n      assert.equal(themeService.colors.ansi[152].css, '#afd7d7');\n      assert.equal(themeService.colors.ansi[153].css, '#afd7ff');\n      assert.equal(themeService.colors.ansi[154].css, '#afff00');\n      assert.equal(themeService.colors.ansi[155].css, '#afff5f');\n      assert.equal(themeService.colors.ansi[156].css, '#afff87');\n      assert.equal(themeService.colors.ansi[157].css, '#afffaf');\n      assert.equal(themeService.colors.ansi[158].css, '#afffd7');\n      assert.equal(themeService.colors.ansi[159].css, '#afffff');\n      assert.equal(themeService.colors.ansi[160].css, '#d70000');\n      assert.equal(themeService.colors.ansi[161].css, '#d7005f');\n      assert.equal(themeService.colors.ansi[162].css, '#d70087');\n      assert.equal(themeService.colors.ansi[163].css, '#d700af');\n      assert.equal(themeService.colors.ansi[164].css, '#d700d7');\n      assert.equal(themeService.colors.ansi[165].css, '#d700ff');\n      assert.equal(themeService.colors.ansi[166].css, '#d75f00');\n      assert.equal(themeService.colors.ansi[167].css, '#d75f5f');\n      assert.equal(themeService.colors.ansi[168].css, '#d75f87');\n      assert.equal(themeService.colors.ansi[169].css, '#d75faf');\n      assert.equal(themeService.colors.ansi[170].css, '#d75fd7');\n      assert.equal(themeService.colors.ansi[171].css, '#d75fff');\n      assert.equal(themeService.colors.ansi[172].css, '#d78700');\n      assert.equal(themeService.colors.ansi[173].css, '#d7875f');\n      assert.equal(themeService.colors.ansi[174].css, '#d78787');\n      assert.equal(themeService.colors.ansi[175].css, '#d787af');\n      assert.equal(themeService.colors.ansi[176].css, '#d787d7');\n      assert.equal(themeService.colors.ansi[177].css, '#d787ff');\n      assert.equal(themeService.colors.ansi[178].css, '#d7af00');\n      assert.equal(themeService.colors.ansi[179].css, '#d7af5f');\n      assert.equal(themeService.colors.ansi[180].css, '#d7af87');\n      assert.equal(themeService.colors.ansi[181].css, '#d7afaf');\n      assert.equal(themeService.colors.ansi[182].css, '#d7afd7');\n      assert.equal(themeService.colors.ansi[183].css, '#d7afff');\n      assert.equal(themeService.colors.ansi[184].css, '#d7d700');\n      assert.equal(themeService.colors.ansi[185].css, '#d7d75f');\n      assert.equal(themeService.colors.ansi[186].css, '#d7d787');\n      assert.equal(themeService.colors.ansi[187].css, '#d7d7af');\n      assert.equal(themeService.colors.ansi[188].css, '#d7d7d7');\n      assert.equal(themeService.colors.ansi[189].css, '#d7d7ff');\n      assert.equal(themeService.colors.ansi[190].css, '#d7ff00');\n      assert.equal(themeService.colors.ansi[191].css, '#d7ff5f');\n      assert.equal(themeService.colors.ansi[192].css, '#d7ff87');\n      assert.equal(themeService.colors.ansi[193].css, '#d7ffaf');\n      assert.equal(themeService.colors.ansi[194].css, '#d7ffd7');\n      assert.equal(themeService.colors.ansi[195].css, '#d7ffff');\n      assert.equal(themeService.colors.ansi[196].css, '#ff0000');\n      assert.equal(themeService.colors.ansi[197].css, '#ff005f');\n      assert.equal(themeService.colors.ansi[198].css, '#ff0087');\n      assert.equal(themeService.colors.ansi[199].css, '#ff00af');\n      assert.equal(themeService.colors.ansi[200].css, '#ff00d7');\n      assert.equal(themeService.colors.ansi[201].css, '#ff00ff');\n      assert.equal(themeService.colors.ansi[202].css, '#ff5f00');\n      assert.equal(themeService.colors.ansi[203].css, '#ff5f5f');\n      assert.equal(themeService.colors.ansi[204].css, '#ff5f87');\n      assert.equal(themeService.colors.ansi[205].css, '#ff5faf');\n      assert.equal(themeService.colors.ansi[206].css, '#ff5fd7');\n      assert.equal(themeService.colors.ansi[207].css, '#ff5fff');\n      assert.equal(themeService.colors.ansi[208].css, '#ff8700');\n      assert.equal(themeService.colors.ansi[209].css, '#ff875f');\n      assert.equal(themeService.colors.ansi[210].css, '#ff8787');\n      assert.equal(themeService.colors.ansi[211].css, '#ff87af');\n      assert.equal(themeService.colors.ansi[212].css, '#ff87d7');\n      assert.equal(themeService.colors.ansi[213].css, '#ff87ff');\n      assert.equal(themeService.colors.ansi[214].css, '#ffaf00');\n      assert.equal(themeService.colors.ansi[215].css, '#ffaf5f');\n      assert.equal(themeService.colors.ansi[216].css, '#ffaf87');\n      assert.equal(themeService.colors.ansi[217].css, '#ffafaf');\n      assert.equal(themeService.colors.ansi[218].css, '#ffafd7');\n      assert.equal(themeService.colors.ansi[219].css, '#ffafff');\n      assert.equal(themeService.colors.ansi[220].css, '#ffd700');\n      assert.equal(themeService.colors.ansi[221].css, '#ffd75f');\n      assert.equal(themeService.colors.ansi[222].css, '#ffd787');\n      assert.equal(themeService.colors.ansi[223].css, '#ffd7af');\n      assert.equal(themeService.colors.ansi[224].css, '#ffd7d7');\n      assert.equal(themeService.colors.ansi[225].css, '#ffd7ff');\n      assert.equal(themeService.colors.ansi[226].css, '#ffff00');\n      assert.equal(themeService.colors.ansi[227].css, '#ffff5f');\n      assert.equal(themeService.colors.ansi[228].css, '#ffff87');\n      assert.equal(themeService.colors.ansi[229].css, '#ffffaf');\n      assert.equal(themeService.colors.ansi[230].css, '#ffffd7');\n      assert.equal(themeService.colors.ansi[231].css, '#ffffff');\n      assert.equal(themeService.colors.ansi[232].css, '#080808');\n      assert.equal(themeService.colors.ansi[233].css, '#121212');\n      assert.equal(themeService.colors.ansi[234].css, '#1c1c1c');\n      assert.equal(themeService.colors.ansi[235].css, '#262626');\n      assert.equal(themeService.colors.ansi[236].css, '#303030');\n      assert.equal(themeService.colors.ansi[237].css, '#3a3a3a');\n      assert.equal(themeService.colors.ansi[238].css, '#444444');\n      assert.equal(themeService.colors.ansi[239].css, '#4e4e4e');\n      assert.equal(themeService.colors.ansi[240].css, '#585858');\n      assert.equal(themeService.colors.ansi[241].css, '#626262');\n      assert.equal(themeService.colors.ansi[242].css, '#6c6c6c');\n      assert.equal(themeService.colors.ansi[243].css, '#767676');\n      assert.equal(themeService.colors.ansi[244].css, '#808080');\n      assert.equal(themeService.colors.ansi[245].css, '#8a8a8a');\n      assert.equal(themeService.colors.ansi[246].css, '#949494');\n      assert.equal(themeService.colors.ansi[247].css, '#9e9e9e');\n      assert.equal(themeService.colors.ansi[248].css, '#a8a8a8');\n      assert.equal(themeService.colors.ansi[249].css, '#b2b2b2');\n      assert.equal(themeService.colors.ansi[250].css, '#bcbcbc');\n      assert.equal(themeService.colors.ansi[251].css, '#c6c6c6');\n      assert.equal(themeService.colors.ansi[252].css, '#d0d0d0');\n      assert.equal(themeService.colors.ansi[253].css, '#dadada');\n      assert.equal(themeService.colors.ansi[254].css, '#e4e4e4');\n      assert.equal(themeService.colors.ansi[255].css, '#eeeeee');\n    });\n  });\n\n  describe('setTheme', () => {\n    it('should not throw when not setting all colors', () => {\n      assert.doesNotThrow(() => {\n        optionsService.options.theme = {};\n      });\n    });\n\n    it('should set a partial set of colors, using the default if not present', () => {\n      assert.equal(themeService.colors.background.css, '#000000');\n      assert.equal(themeService.colors.foreground.css, '#ffffff');\n      optionsService.options.theme = {\n        background: '#FF0000',\n        foreground: '#00FF00'\n      };\n      assert.equal(themeService.colors.background.css, '#FF0000');\n      assert.equal(themeService.colors.foreground.css, '#00FF00');\n      optionsService.options.theme = {\n        background: '#0000FF'\n      };\n      assert.equal(themeService.colors.background.css, '#0000FF');\n      // FG reverts back to default\n      assert.equal(themeService.colors.foreground.css, '#ffffff');\n    });\n\n    it('should set all extended ansi colors in reverse order', () => {\n      optionsService.options.theme = {\n        extendedAnsi: DEFAULT_ANSI_COLORS.map(a => a.css).slice().reverse()\n      };\n\n      for (let ansiColor = 16; ansiColor <= 255; ansiColor++) {\n        assert.equal(themeService.colors.ansi[ansiColor].css, DEFAULT_ANSI_COLORS[255 + 16 - ansiColor].css);\n      }\n    });\n\n    it('should set one extended ansi color and keep the other default', () => {\n      optionsService.options.theme = {\n        extendedAnsi: ['#ffffff']\n      };\n\n      assert.equal(themeService.colors.ansi[16].css, '#ffffff');\n      assert.equal(themeService.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css);\n    });\n\n    it('should set extended ansi colors to the default when they are unset', () => {\n      optionsService.options.theme = {\n        extendedAnsi: ['#ffffff']\n      };\n      assert.equal(themeService.colors.ansi[16].css, '#ffffff');\n\n      optionsService.options.theme = {\n        extendedAnsi: []\n      };\n      assert.equal(themeService.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css);\n\n      optionsService.options.theme = {\n        extendedAnsi: ['#ffffff']\n      };\n      assert.equal(themeService.colors.ansi[16].css, '#ffffff');\n\n      optionsService.options.theme = {};\n      assert.equal(themeService.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css);\n    });\n\n    it('should set extended ansi colors to the default when they are partially unset', () => {\n      optionsService.options.theme = {\n        extendedAnsi: ['#ffffff', '#000000']\n      };\n      assert.equal(themeService.colors.ansi[16].css, '#ffffff');\n      assert.equal(themeService.colors.ansi[17].css, '#000000');\n\n      optionsService.options.theme = {\n        extendedAnsi: ['#ffffff']\n      };\n      assert.equal(themeService.colors.ansi[16].css, '#ffffff');\n      assert.equal(themeService.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css);\n    });\n  });\n});\n"
  },
  {
    "path": "src/browser/services/ThemeService.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ColorContrastCache } from 'browser/ColorContrastCache';\nimport { IThemeService } from 'browser/services/Services';\nimport { DEFAULT_ANSI_COLORS, IColorContrastCache, IColorSet, ReadonlyColorSet } from 'browser/Types';\nimport { color, css, NULL_COLOR } from 'common/Color';\nimport { Disposable } from 'common/Lifecycle';\nimport { IOptionsService, ITheme } from 'common/services/Services';\nimport { AllColorIndex, IColor, SpecialColorIndex } from 'common/Types';\nimport { Emitter } from 'common/Event';\n\ninterface IRestoreColorSet {\n  foreground: IColor;\n  background: IColor;\n  cursor: IColor;\n  ansi: IColor[];\n}\n\n\nconst DEFAULT_FOREGROUND = css.toColor('#ffffff');\nconst DEFAULT_BACKGROUND = css.toColor('#000000');\nconst DEFAULT_CURSOR = css.toColor('#ffffff');\nconst DEFAULT_CURSOR_ACCENT = DEFAULT_BACKGROUND;\nconst DEFAULT_SELECTION = {\n  css: 'rgba(255, 255, 255, 0.3)',\n  rgba: 0xFFFFFF4D\n};\nconst DEFAULT_OVERVIEW_RULER_BORDER = DEFAULT_FOREGROUND;\n\nexport class ThemeService extends Disposable implements IThemeService {\n  public serviceBrand: undefined;\n\n  private _colors: IColorSet;\n  private _contrastCache: IColorContrastCache = new ColorContrastCache();\n  private _halfContrastCache: IColorContrastCache = new ColorContrastCache();\n  private _restoreColors!: IRestoreColorSet;\n\n  public get colors(): ReadonlyColorSet { return this._colors; }\n\n  private readonly _onChangeColors = this._register(new Emitter<ReadonlyColorSet>());\n  public readonly onChangeColors = this._onChangeColors.event;\n\n  constructor(\n    @IOptionsService private readonly _optionsService: IOptionsService\n  ) {\n    super();\n\n    this._colors = {\n      foreground: DEFAULT_FOREGROUND,\n      background: DEFAULT_BACKGROUND,\n      cursor: DEFAULT_CURSOR,\n      cursorAccent: DEFAULT_CURSOR_ACCENT,\n      selectionForeground: undefined,\n      selectionBackgroundTransparent: DEFAULT_SELECTION,\n      selectionBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),\n      selectionInactiveBackgroundTransparent: DEFAULT_SELECTION,\n      selectionInactiveBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),\n      scrollbarSliderBackground: color.opacity(DEFAULT_FOREGROUND, 0.2),\n      scrollbarSliderHoverBackground: color.opacity(DEFAULT_FOREGROUND, 0.4),\n      scrollbarSliderActiveBackground: color.opacity(DEFAULT_FOREGROUND, 0.5),\n      overviewRulerBorder: DEFAULT_FOREGROUND,\n      ansi: DEFAULT_ANSI_COLORS.slice(),\n      contrastCache: this._contrastCache,\n      halfContrastCache: this._halfContrastCache\n    };\n    this._updateRestoreColors();\n    this._setTheme(this._optionsService.rawOptions.theme);\n\n    this._register(this._optionsService.onSpecificOptionChange('minimumContrastRatio', () => this._contrastCache.clear()));\n    this._register(this._optionsService.onSpecificOptionChange('theme', () => this._setTheme(this._optionsService.rawOptions.theme)));\n  }\n\n  /**\n   * Sets the terminal's theme.\n   * @param theme The  theme to use. If a partial theme is provided then default\n   * colors will be used where colors are not defined.\n   */\n  private _setTheme(theme: ITheme = {}): void {\n    const colors = this._colors;\n    colors.foreground = parseColor(theme.foreground, DEFAULT_FOREGROUND);\n    colors.background = parseColor(theme.background, DEFAULT_BACKGROUND);\n    colors.cursor = color.blend(colors.background, parseColor(theme.cursor, DEFAULT_CURSOR));\n    colors.cursorAccent = color.blend(colors.background, parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT));\n    colors.selectionBackgroundTransparent = parseColor(theme.selectionBackground, DEFAULT_SELECTION);\n    colors.selectionBackgroundOpaque = color.blend(colors.background, colors.selectionBackgroundTransparent);\n    colors.selectionInactiveBackgroundTransparent = parseColor(theme.selectionInactiveBackground, colors.selectionBackgroundTransparent);\n    colors.selectionInactiveBackgroundOpaque = color.blend(colors.background, colors.selectionInactiveBackgroundTransparent);\n    colors.selectionForeground = theme.selectionForeground ? parseColor(theme.selectionForeground, NULL_COLOR) : undefined;\n    if (colors.selectionForeground === NULL_COLOR) {\n      colors.selectionForeground = undefined;\n    }\n\n    /**\n     * If selection color is opaque, blend it with background with 0.3 opacity\n     * Issue #2737\n     */\n    if (color.isOpaque(colors.selectionBackgroundTransparent)) {\n      const opacity = 0.3;\n      colors.selectionBackgroundTransparent = color.opacity(colors.selectionBackgroundTransparent, opacity);\n    }\n    if (color.isOpaque(colors.selectionInactiveBackgroundTransparent)) {\n      const opacity = 0.3;\n      colors.selectionInactiveBackgroundTransparent = color.opacity(colors.selectionInactiveBackgroundTransparent, opacity);\n    }\n    colors.scrollbarSliderBackground = parseColor(theme.scrollbarSliderBackground, color.opacity(colors.foreground, 0.2));\n    colors.scrollbarSliderHoverBackground = parseColor(theme.scrollbarSliderHoverBackground, color.opacity(colors.foreground, 0.4));\n    colors.scrollbarSliderActiveBackground = parseColor(theme.scrollbarSliderActiveBackground, color.opacity(colors.foreground, 0.5));\n    colors.overviewRulerBorder = parseColor(theme.overviewRulerBorder, DEFAULT_OVERVIEW_RULER_BORDER);\n    colors.ansi = DEFAULT_ANSI_COLORS.slice();\n    colors.ansi[0] = parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);\n    colors.ansi[1] = parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);\n    colors.ansi[2] = parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);\n    colors.ansi[3] = parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]);\n    colors.ansi[4] = parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]);\n    colors.ansi[5] = parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]);\n    colors.ansi[6] = parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]);\n    colors.ansi[7] = parseColor(theme.white, DEFAULT_ANSI_COLORS[7]);\n    colors.ansi[8] = parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]);\n    colors.ansi[9] = parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]);\n    colors.ansi[10] = parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]);\n    colors.ansi[11] = parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]);\n    colors.ansi[12] = parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]);\n    colors.ansi[13] = parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);\n    colors.ansi[14] = parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);\n    colors.ansi[15] = parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);\n    if (theme.extendedAnsi) {\n      const colorCount = Math.min(colors.ansi.length - 16, theme.extendedAnsi.length);\n      for (let i = 0; i < colorCount; i++) {\n        colors.ansi[i + 16] = parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]);\n      }\n    }\n    // Clear our the cache\n    this._contrastCache.clear();\n    this._halfContrastCache.clear();\n    this._updateRestoreColors();\n    this._onChangeColors.fire(this.colors);\n  }\n\n  public restoreColor(slot?: AllColorIndex): void {\n    this._restoreColor(slot);\n    this._onChangeColors.fire(this.colors);\n  }\n\n  private _restoreColor(slot: AllColorIndex | undefined): void {\n    // unset slot restores all ansi colors\n    if (slot === undefined) {\n      for (let i = 0; i < this._restoreColors.ansi.length; ++i) {\n        this._colors.ansi[i] = this._restoreColors.ansi[i];\n      }\n      return;\n    }\n    switch (slot) {\n      case SpecialColorIndex.FOREGROUND:\n        this._colors.foreground = this._restoreColors.foreground;\n        break;\n      case SpecialColorIndex.BACKGROUND:\n        this._colors.background = this._restoreColors.background;\n        break;\n      case SpecialColorIndex.CURSOR:\n        this._colors.cursor = this._restoreColors.cursor;\n        break;\n      default:\n        this._colors.ansi[slot] = this._restoreColors.ansi[slot];\n    }\n  }\n\n  public modifyColors(callback: (colors: IColorSet) => void): void {\n    callback(this._colors);\n    // Assume the change happened\n    this._onChangeColors.fire(this.colors);\n  }\n\n  private _updateRestoreColors(): void {\n    this._restoreColors = {\n      foreground: this._colors.foreground,\n      background: this._colors.background,\n      cursor: this._colors.cursor,\n      ansi: this._colors.ansi.slice()\n    };\n  }\n}\n\nfunction parseColor(\n  cssString: string | undefined,\n  fallback: IColor\n): IColor {\n  if (cssString !== undefined) {\n    try {\n      return css.toColor(cssString);\n    } catch {\n      // no-op\n    }\n  }\n  return fallback;\n}\n"
  },
  {
    "path": "src/browser/shared/Constants.ts",
    "content": "/**\n * Copyright (c) 2024 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport const enum ViewportConstants {\n  DEFAULT_SCROLL_BAR_WIDTH = 14\n}\n"
  },
  {
    "path": "src/browser/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig-library-base\",\n  \"compilerOptions\": {\n    \"lib\": [\n      \"dom\",\n      \"es2021\"\n    ],\n    \"outDir\": \"../../out\",\n    \"types\": [\n      \"../../node_modules/@types/mocha\"\n    ],\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"common/*\": [ \"./../common/*\" ],\n      \"*\": [ \"./../*\" ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    { \"path\": \"../common\" }\n  ]\n}\n"
  },
  {
    "path": "src/common/Async.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Minimal async helpers for xterm.js core.\n */\n\nimport { DisposableStore, IDisposable, toDisposable } from 'common/Lifecycle';\n\nexport function timeout(millis: number): Promise<void> {\n  return new Promise(resolve => setTimeout(resolve, millis));\n}\n\n/**\n * Creates a timeout that can be disposed using its returned value.\n * @param handler The timeout handler.\n * @param timeout An optional timeout in milliseconds.\n * @param store An optional {@link DisposableStore} that will have the timeout disposable managed\n * automatically.\n */\nexport function disposableTimeout(handler: () => void, timeout = 0, store?: DisposableStore): IDisposable {\n  const timer = setTimeout(() => {\n    handler();\n    if (store) {\n      disposable.dispose();\n    }\n  }, timeout);\n  const disposable = toDisposable(() => {\n    clearTimeout(timer);\n  });\n  store?.add(disposable);\n  return disposable;\n}\n\nexport class TimeoutTimer implements IDisposable {\n  private _token: any = -1;\n  private _isDisposed = false;\n\n  public dispose(): void {\n    this.cancel();\n    this._isDisposed = true;\n  }\n\n  public cancel(): void {\n    if (this._token !== -1) {\n      clearTimeout(this._token);\n      this._token = -1;\n    }\n  }\n\n  public cancelAndSet(runner: () => void, timeout: number): void {\n    if (this._isDisposed) {\n      throw new Error('Calling cancelAndSet on a disposed TimeoutTimer');\n    }\n    this.cancel();\n    this._token = setTimeout(() => {\n      this._token = -1;\n      runner();\n    }, timeout);\n  }\n\n  public setIfNotSet(runner: () => void, timeout: number): void {\n    if (this._isDisposed) {\n      throw new Error('Calling setIfNotSet on a disposed TimeoutTimer');\n    }\n    if (this._token !== -1) {\n      return;\n    }\n    this._token = setTimeout(() => {\n      this._token = -1;\n      runner();\n    }, timeout);\n  }\n}\n\nexport class IntervalTimer implements IDisposable {\n  private _disposable: IDisposable | undefined;\n  private _isDisposed = false;\n\n  public cancel(): void {\n    this._disposable?.dispose();\n    this._disposable = undefined;\n  }\n\n  public cancelAndSet(runner: () => void, interval: number, context: Window | typeof globalThis = globalThis): void {\n    if (this._isDisposed) {\n      throw new Error('Calling cancelAndSet on a disposed IntervalTimer');\n    }\n    this.cancel();\n    const handle = context.setInterval(() => {\n      runner();\n    }, interval);\n    this._disposable = {\n      dispose: () => {\n        context.clearInterval(handle as any);\n        this._disposable = undefined;\n      }\n    };\n  }\n\n  public dispose(): void {\n    this.cancel();\n    this._isDisposed = true;\n  }\n}\n"
  },
  {
    "path": "src/common/CircularList.test.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { CircularList } from 'common/CircularList';\n\ndescribe('CircularList', () => {\n  describe('push', () => {\n    it('should push values onto the array', () => {\n      const list = new CircularList<string>(5);\n      list.push('1');\n      list.push('2');\n      list.push('3');\n      list.push('4');\n      list.push('5');\n      assert.equal(list.get(0), '1');\n      assert.equal(list.get(1), '2');\n      assert.equal(list.get(2), '3');\n      assert.equal(list.get(3), '4');\n      assert.equal(list.get(4), '5');\n    });\n\n    it('should push old values from the start out of the array when max length is reached', () => {\n      const list = new CircularList<string>(2);\n      list.push('1');\n      list.push('2');\n      assert.equal(list.get(0), '1');\n      assert.equal(list.get(1), '2');\n      list.push('3');\n      assert.equal(list.get(0), '2');\n      assert.equal(list.get(1), '3');\n      list.push('4');\n      assert.equal(list.get(0), '3');\n      assert.equal(list.get(1), '4');\n    });\n  });\n\n  describe('maxLength', () => {\n    it('should increase the size of the list', () => {\n      const list = new CircularList<string>(2);\n      list.push('1');\n      list.push('2');\n      assert.equal(list.get(0), '1');\n      assert.equal(list.get(1), '2');\n      list.maxLength = 4;\n      list.push('3');\n      list.push('4');\n      assert.equal(list.get(0), '1');\n      assert.equal(list.get(1), '2');\n      assert.equal(list.get(2), '3');\n      assert.equal(list.get(3), '4');\n      list.push('wrapped');\n      assert.equal(list.get(0), '2');\n      assert.equal(list.get(1), '3');\n      assert.equal(list.get(2), '4');\n      assert.equal(list.get(3), 'wrapped');\n    });\n\n    it('should return the maximum length of the list', () => {\n      const list = new CircularList<string>(2);\n      assert.equal(list.maxLength, 2);\n      list.push('1');\n      list.push('2');\n      assert.equal(list.maxLength, 2);\n      list.push('3');\n      assert.equal(list.maxLength, 2);\n      list.maxLength = 4;\n      assert.equal(list.maxLength, 4);\n    });\n  });\n\n  describe('length', () => {\n    it('should return the current length of the list, capped at the maximum length', () => {\n      const list = new CircularList<string>(2);\n      assert.equal(list.length, 0);\n      list.push('1');\n      assert.equal(list.length, 1);\n      list.push('2');\n      assert.equal(list.length, 2);\n      list.push('3');\n      assert.equal(list.length, 2);\n    });\n  });\n\n  describe('splice', () => {\n    it('should delete items', () => {\n      const list = new CircularList<string>(2);\n      list.push('1');\n      list.push('2');\n      list.splice(0, 1);\n      assert.equal(list.length, 1);\n      assert.equal(list.get(0), '2');\n      list.push('3');\n      list.splice(1, 1);\n      assert.equal(list.length, 1);\n      assert.equal(list.get(0), '2');\n    });\n\n    it('should insert items', () => {\n      const list = new CircularList<string>(2);\n      list.push('1');\n      list.splice(0, 0, '2');\n      assert.equal(list.length, 2);\n      assert.equal(list.get(0), '2');\n      assert.equal(list.get(1), '1');\n      list.splice(1, 0, '3');\n      assert.equal(list.length, 2);\n      assert.equal(list.get(0), '3');\n      assert.equal(list.get(1), '1');\n    });\n\n    it('should delete items then insert items', () => {\n      const list = new CircularList<string>(3);\n      list.push('1');\n      list.push('2');\n      list.splice(0, 1, '3', '4');\n      assert.equal(list.length, 3);\n      assert.equal(list.get(0), '3');\n      assert.equal(list.get(1), '4');\n      assert.equal(list.get(2), '2');\n    });\n\n    it('should wrap the array correctly when more items are inserted than deleted', () => {\n      const list = new CircularList<string>(3);\n      list.push('1');\n      list.push('2');\n      list.splice(1, 0, '3', '4');\n      assert.equal(list.length, 3);\n      assert.equal(list.get(0), '3');\n      assert.equal(list.get(1), '4');\n      assert.equal(list.get(2), '2');\n    });\n  });\n\n  describe('trimStart', () => {\n    it('should remove items from the beginning of the list', () => {\n      const list = new CircularList<string>(5);\n      list.push('1');\n      list.push('2');\n      list.push('3');\n      list.push('4');\n      list.push('5');\n      list.trimStart(1);\n      assert.equal(list.length, 4);\n      assert.deepEqual(list.get(0), '2');\n      assert.deepEqual(list.get(1), '3');\n      assert.deepEqual(list.get(2), '4');\n      assert.deepEqual(list.get(3), '5');\n      list.trimStart(2);\n      assert.equal(list.length, 2);\n      assert.deepEqual(list.get(0), '4');\n      assert.deepEqual(list.get(1), '5');\n    });\n\n    it('should remove all items if the requested trim amount is larger than the list\\'s length', () => {\n      const list = new CircularList<string>(5);\n      list.push('1');\n      list.trimStart(2);\n      assert.equal(list.length, 0);\n    });\n  });\n\n  describe('shiftElements', () => {\n    it('should not mutate the list when count is 0', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.shiftElements(0, 0, 1);\n      assert.equal(list.length, 2);\n      assert.equal(list.get(0), 1);\n      assert.equal(list.get(1), 2);\n    });\n\n    it('should throw for invalid args', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      assert.throws(() => list.shiftElements(-1, 1, 1), 'start argument out of range');\n      assert.throws(() => list.shiftElements(1, 1, 1), 'start argument out of range');\n      assert.throws(() => list.shiftElements(0, 1, -1), 'Cannot shift elements in list beyond index 0');\n    });\n\n    it('should shift an element forward', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.shiftElements(0, 1, 1);\n      assert.equal(list.length, 2);\n      assert.equal(list.get(0), 1);\n      assert.equal(list.get(1), 1);\n    });\n\n    it('should shift elements forward', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.push(3);\n      list.push(4);\n      list.shiftElements(0, 2, 2);\n      assert.equal(list.length, 4);\n      assert.equal(list.get(0), 1);\n      assert.equal(list.get(1), 2);\n      assert.equal(list.get(2), 1);\n      assert.equal(list.get(3), 2);\n    });\n\n    it('should shift elements forward, expanding the list if needed', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.shiftElements(0, 2, 2);\n      assert.equal(list.length, 4);\n      assert.equal(list.get(0), 1);\n      assert.equal(list.get(1), 2);\n      assert.equal(list.get(2), 1);\n      assert.equal(list.get(3), 2);\n    });\n\n    it('should shift elements forward, wrapping the list if needed', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.push(3);\n      list.push(4);\n      list.push(5);\n      list.shiftElements(2, 2, 3);\n      assert.equal(list.length, 5);\n      assert.equal(list.get(0), 3);\n      assert.equal(list.get(1), 4);\n      assert.equal(list.get(2), 5);\n      assert.equal(list.get(3), 3);\n      assert.equal(list.get(4), 4);\n    });\n\n    it('should shift an element backwards', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.shiftElements(1, 1, -1);\n      assert.equal(list.length, 2);\n      assert.equal(list.get(0), 2);\n      assert.equal(list.get(1), 2);\n    });\n\n    it('should shift elements backwards', () => {\n      const list = new CircularList<number>(5);\n      list.push(1);\n      list.push(2);\n      list.push(3);\n      list.push(4);\n      list.shiftElements(2, 2, -2);\n      assert.equal(list.length, 4);\n      assert.equal(list.get(0), 3);\n      assert.equal(list.get(1), 4);\n      assert.equal(list.get(2), 3);\n      assert.equal(list.get(3), 4);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/CircularList.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICircularList } from 'common/Types';\nimport { Disposable } from 'common/Lifecycle';\nimport { Emitter } from 'common/Event';\n\nexport interface IInsertEvent {\n  index: number;\n  amount: number;\n}\n\nexport interface IDeleteEvent {\n  index: number;\n  amount: number;\n}\n\n/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n */\nexport class CircularList<T> extends Disposable implements ICircularList<T> {\n  protected _array: (T | undefined)[];\n  private _startIndex: number;\n  private _length: number;\n\n  public readonly onDeleteEmitter = this._register(new Emitter<IDeleteEvent>());\n  public readonly onDelete = this.onDeleteEmitter.event;\n  public readonly onInsertEmitter = this._register(new Emitter<IInsertEvent>());\n  public readonly onInsert = this.onInsertEmitter.event;\n  public readonly onTrimEmitter = this._register(new Emitter<number>());\n  public readonly onTrim = this.onTrimEmitter.event;\n\n  constructor(\n    private _maxLength: number\n  ) {\n    super();\n    this._array = new Array<T>(this._maxLength);\n    this._startIndex = 0;\n    this._length = 0;\n  }\n\n  public get maxLength(): number {\n    return this._maxLength;\n  }\n\n  public set maxLength(newMaxLength: number) {\n    // There was no change in maxLength, return early.\n    if (this._maxLength === newMaxLength) {\n      return;\n    }\n\n    // Reconstruct array, starting at index 0. Only transfer values from the\n    // indexes 0 to length.\n    const newArray = new Array<T | undefined>(newMaxLength);\n    for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n      newArray[i] = this._array[this._getCyclicIndex(i)];\n    }\n    this._array = newArray;\n    this._maxLength = newMaxLength;\n    this._startIndex = 0;\n  }\n\n  public get length(): number {\n    return this._length;\n  }\n\n  public set length(newLength: number) {\n    if (newLength > this._length) {\n      for (let i = this._length; i < newLength; i++) {\n        this._array[i] = undefined;\n      }\n    }\n    this._length = newLength;\n  }\n\n  /**\n   * Gets the value at an index.\n   *\n   * Note that for performance reasons there is no bounds checking here, the index reference is\n   * circular so this should always return a value and never throw.\n   * @param index The index of the value to get.\n   * @returns The value corresponding to the index.\n   */\n  public get(index: number): T | undefined {\n    return this._array[this._getCyclicIndex(index)];\n  }\n\n  /**\n   * Sets the value at an index.\n   *\n   * Note that for performance reasons there is no bounds checking here, the index reference is\n   * circular so this should always return a value and never throw.\n   * @param index The index to set.\n   * @param value The value to set.\n   */\n  public set(index: number, value: T | undefined): void {\n    this._array[this._getCyclicIndex(index)] = value;\n  }\n\n  /**\n   * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n   * if the maximum length is reached.\n   * @param value The value to push onto the list.\n   */\n  public push(value: T): void {\n    this._array[this._getCyclicIndex(this._length)] = value;\n    if (this._length === this._maxLength) {\n      this._startIndex = ++this._startIndex % this._maxLength;\n      this.onTrimEmitter.fire(1);\n    } else {\n      this._length++;\n    }\n  }\n\n  /**\n   * Advance ringbuffer index and return current element for recycling.\n   * Note: The buffer must be full for this method to work.\n   * @throws When the buffer is not full.\n   */\n  public recycle(): T {\n    if (this._length !== this._maxLength) {\n      throw new Error('Can only recycle when the buffer is full');\n    }\n    this._startIndex = ++this._startIndex % this._maxLength;\n    this.onTrimEmitter.fire(1);\n    return this._array[this._getCyclicIndex(this._length - 1)]!;\n  }\n\n  /**\n   * Ringbuffer is at max length.\n   */\n  public get isFull(): boolean {\n    return this._length === this._maxLength;\n  }\n\n  /**\n   * Removes and returns the last value on the list.\n   * @returns The popped value.\n   */\n  public pop(): T | undefined {\n    return this._array[this._getCyclicIndex(this._length-- - 1)];\n  }\n\n  /**\n   * Deletes and/or inserts items at a particular index (in that order). Unlike\n   * Array.prototype.splice, this operation does not return the deleted items as a new array in\n   * order to save creating a new array. Note that this operation may shift all values in the list\n   * in the worst case.\n   * @param start The index to delete and/or insert.\n   * @param deleteCount The number of elements to delete.\n   * @param items The items to insert.\n   */\n  public splice(start: number, deleteCount: number, ...items: T[]): void {\n    // Delete items\n    if (deleteCount) {\n      for (let i = start; i < this._length - deleteCount; i++) {\n        this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n      }\n      this._length -= deleteCount;\n      this.onDeleteEmitter.fire({ index: start, amount: deleteCount });\n    }\n\n    // Add items\n    for (let i = this._length - 1; i >= start; i--) {\n      this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n    }\n    for (let i = 0; i < items.length; i++) {\n      this._array[this._getCyclicIndex(start + i)] = items[i];\n    }\n    if (items.length) {\n      this.onInsertEmitter.fire({ index: start, amount: items.length });\n    }\n\n    // Adjust length as needed\n    if (this._length + items.length > this._maxLength) {\n      const countToTrim = (this._length + items.length) - this._maxLength;\n      this._startIndex += countToTrim;\n      this._length = this._maxLength;\n      this.onTrimEmitter.fire(countToTrim);\n    } else {\n      this._length += items.length;\n    }\n  }\n\n  /**\n   * Trims a number of items from the start of the list.\n   * @param count The number of items to remove.\n   */\n  public trimStart(count: number): void {\n    if (count > this._length) {\n      count = this._length;\n    }\n    this._startIndex += count;\n    this._length -= count;\n    this.onTrimEmitter.fire(count);\n  }\n\n  public shiftElements(start: number, count: number, offset: number): void {\n    if (count <= 0) {\n      return;\n    }\n    if (start < 0 || start >= this._length) {\n      throw new Error('start argument out of range');\n    }\n    if (start + offset < 0) {\n      throw new Error('Cannot shift elements in list beyond index 0');\n    }\n\n    if (offset > 0) {\n      for (let i = count - 1; i >= 0; i--) {\n        this.set(start + i + offset, this.get(start + i));\n      }\n      const expandListBy = (start + count + offset) - this._length;\n      if (expandListBy > 0) {\n        this._length += expandListBy;\n        while (this._length > this._maxLength) {\n          this._length--;\n          this._startIndex++;\n          this.onTrimEmitter.fire(1);\n        }\n      }\n    } else {\n      for (let i = 0; i < count; i++) {\n        this.set(start + i + offset, this.get(start + i));\n      }\n    }\n  }\n\n  /**\n   * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n   * backing array to get the element associated with the regular index.\n   * @param index The regular index.\n   * @returns The cyclic index.\n   */\n  private _getCyclicIndex(index: number): number {\n    return (this._startIndex + index) % this._maxLength;\n  }\n}\n"
  },
  {
    "path": "src/common/Clone.test.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { clone } from 'common/Clone';\n\ndescribe('clone', () => {\n  it('should clone simple objects', () => {\n    const test = {\n      a: 1,\n      b: 2\n    };\n\n    assert.deepEqual(clone(test), { a: 1, b: 2 });\n  });\n\n  it('should clone nested objects', () => {\n    const test = {\n      bar: {\n        a: 1,\n        b: 2,\n        c: {\n          foo: 'bar'\n        }\n      }\n    };\n\n    assert.deepEqual(clone(test), {\n      bar: {\n        a: 1,\n        b: 2,\n        c: {\n          foo: 'bar'\n        }\n      }\n    });\n  });\n\n  it('should clone array values', () => {\n    const test = {\n      a: [1, 2, 3],\n      b: [1, null, 'test', { foo: 'bar' }]\n    };\n\n    assert.deepEqual(clone(test), {\n      a: [1, 2, 3],\n      b: [1, null, 'test', { foo: 'bar' }]\n    });\n  });\n\n  it('should stop mutation from occuring on the original object', () => {\n    const test = {\n      a: 1,\n      b: 2,\n      c: {\n        foo: 'bar'\n      }\n    };\n\n    const cloned = clone(test);\n\n    test.a = 5;\n    test.c.foo = 'barbaz';\n\n    assert.deepEqual(cloned, {\n      a: 1,\n      b: 2,\n      c: {\n        foo: 'bar'\n      }\n    });\n  });\n\n  it('should clone to a maximum depth of 5 by default', () => {\n    const test = {\n      a: {\n        b: {\n          c: {\n            d: {\n              e: {\n                f: 'foo'\n              }\n            }\n          }\n        }\n      }\n    };\n\n    const cloned = clone(test);\n\n    test.a.b.c.d.e.f = 'bar';\n\n    // The values at a greater depth then 5 should not be cloned\n    assert.equal((cloned as any).a.b.c.d.e.f, 'bar');\n  });\n\n  it('should allow an optional maximum depth to be set', () => {\n    const test = {\n      a: {\n        b: {\n          c: 'foo'\n        }\n      }\n    };\n\n    const cloned = clone(test, 2);\n\n    test.a.b.c = 'bar';\n\n    // The values at a greater depth then 2 should not be cloned\n    assert.equal((cloned as any).a.b.c, 'bar');\n  });\n\n  it('should not throw when cloning a recursive reference', () => {\n    const test = {\n      a: {\n        b: {\n          c: {}\n        }\n      }\n    };\n\n    test.a.b.c = test;\n\n    assert.doesNotThrow(() => clone(test));\n  });\n});\n"
  },
  {
    "path": "src/common/Clone.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/*\n * A simple utility for cloning values\n */\nexport function clone<T>(val: T, depth: number = 5): T {\n  if (typeof val !== 'object') {\n    return val;\n  }\n\n  // If we're cloning an array, use an array as the base, otherwise use an object\n  const clonedObject: any = Array.isArray(val) ? [] : {};\n\n  for (const key in val) {\n    // Recursively clone eack item unless we're at the maximum depth\n    clonedObject[key] = depth <= 1 ? val[key] : (val[key] && clone(val[key], depth - 1));\n  }\n\n  return clonedObject as T;\n}\n"
  },
  {
    "path": "src/common/Color.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'common/Color';\n\ndescribe('Color', () => {\n\n  describe('channels', () => {\n    describe('toCss', () => {\n      it('should convert an rgb array to css hex string', () => {\n        assert.equal(channels.toCss(0x00, 0x00, 0x00), '#000000');\n        assert.equal(channels.toCss(0x10, 0x10, 0x10), '#101010');\n        assert.equal(channels.toCss(0x20, 0x20, 0x20), '#202020');\n        assert.equal(channels.toCss(0x30, 0x30, 0x30), '#303030');\n        assert.equal(channels.toCss(0x40, 0x40, 0x40), '#404040');\n        assert.equal(channels.toCss(0x50, 0x50, 0x50), '#505050');\n        assert.equal(channels.toCss(0x60, 0x60, 0x60), '#606060');\n        assert.equal(channels.toCss(0x70, 0x70, 0x70), '#707070');\n        assert.equal(channels.toCss(0x80, 0x80, 0x80), '#808080');\n        assert.equal(channels.toCss(0x90, 0x90, 0x90), '#909090');\n        assert.equal(channels.toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');\n        assert.equal(channels.toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');\n        assert.equal(channels.toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');\n        assert.equal(channels.toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');\n        assert.equal(channels.toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');\n        assert.equal(channels.toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');\n        assert.equal(channels.toCss(0xff, 0xff, 0xff), '#ffffff');\n      });\n      it('should convert an rgba array to css hex string', () => {\n        assert.equal(channels.toCss(0x00, 0x00, 0x00, 0x00), '#00000000');\n        assert.equal(channels.toCss(0x10, 0x10, 0x10, 0x10), '#10101010');\n        assert.equal(channels.toCss(0x20, 0x20, 0x20, 0x20), '#20202020');\n        assert.equal(channels.toCss(0x30, 0x30, 0x30, 0x30), '#30303030');\n        assert.equal(channels.toCss(0x40, 0x40, 0x40, 0x40), '#40404040');\n        assert.equal(channels.toCss(0x50, 0x50, 0x50, 0x50), '#50505050');\n        assert.equal(channels.toCss(0x60, 0x60, 0x60, 0x60), '#60606060');\n        assert.equal(channels.toCss(0x70, 0x70, 0x70, 0x70), '#70707070');\n        assert.equal(channels.toCss(0x80, 0x80, 0x80, 0x80), '#80808080');\n        assert.equal(channels.toCss(0x90, 0x90, 0x90, 0x90), '#90909090');\n        assert.equal(channels.toCss(0xa0, 0xa0, 0xa0, 0xa0), '#a0a0a0a0');\n        assert.equal(channels.toCss(0xb0, 0xb0, 0xb0, 0xb0), '#b0b0b0b0');\n        assert.equal(channels.toCss(0xc0, 0xc0, 0xc0, 0xc0), '#c0c0c0c0');\n        assert.equal(channels.toCss(0xd0, 0xd0, 0xd0, 0xd0), '#d0d0d0d0');\n        assert.equal(channels.toCss(0xe0, 0xe0, 0xe0, 0xe0), '#e0e0e0e0');\n        assert.equal(channels.toCss(0xf0, 0xf0, 0xf0, 0xf0), '#f0f0f0f0');\n        assert.equal(channels.toCss(0xff, 0xff, 0xff, 0xff), '#ffffffff');\n      });\n    });\n\n    describe('toRgba', () => {\n      it('should convert an rgb array to an rgba number', () => {\n        assert.equal(channels.toRgba(0x00, 0x00, 0x00), 0x000000FF);\n        assert.equal(channels.toRgba(0x10, 0x10, 0x10), 0x101010FF);\n        assert.equal(channels.toRgba(0x20, 0x20, 0x20), 0x202020FF);\n        assert.equal(channels.toRgba(0x30, 0x30, 0x30), 0x303030FF);\n        assert.equal(channels.toRgba(0x40, 0x40, 0x40), 0x404040FF);\n        assert.equal(channels.toRgba(0x50, 0x50, 0x50), 0x505050FF);\n        assert.equal(channels.toRgba(0x60, 0x60, 0x60), 0x606060FF);\n        assert.equal(channels.toRgba(0x70, 0x70, 0x70), 0x707070FF);\n        assert.equal(channels.toRgba(0x80, 0x80, 0x80), 0x808080FF);\n        assert.equal(channels.toRgba(0x90, 0x90, 0x90), 0x909090FF);\n        assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);\n        assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);\n        assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);\n        assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);\n        assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);\n        assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);\n        assert.equal(channels.toRgba(0xff, 0xff, 0xff), 0xffffffFF);\n      });\n      it('should convert an rgba array to an rgba number', () => {\n        assert.equal(channels.toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);\n        assert.equal(channels.toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);\n        assert.equal(channels.toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);\n        assert.equal(channels.toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);\n        assert.equal(channels.toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);\n        assert.equal(channels.toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);\n        assert.equal(channels.toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);\n        assert.equal(channels.toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);\n        assert.equal(channels.toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);\n        assert.equal(channels.toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);\n        assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);\n        assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);\n        assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);\n        assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);\n        assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);\n        assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);\n        assert.equal(channels.toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);\n      });\n    });\n\n    describe('toColor', () => {\n      it('should convert an rgb array to an IColor', () => {\n        assert.deepStrictEqual(channels.toColor(0x00, 0x00, 0x00), { css: '#000000', rgba: 0x000000FF });\n        assert.deepStrictEqual(channels.toColor(0x10, 0x10, 0x10), { css: '#101010', rgba: 0x101010FF });\n        assert.deepStrictEqual(channels.toColor(0x20, 0x20, 0x20), { css: '#202020', rgba: 0x202020FF });\n        assert.deepStrictEqual(channels.toColor(0x30, 0x30, 0x30), { css: '#303030', rgba: 0x303030FF });\n        assert.deepStrictEqual(channels.toColor(0x40, 0x40, 0x40), { css: '#404040', rgba: 0x404040FF });\n        assert.deepStrictEqual(channels.toColor(0x50, 0x50, 0x50), { css: '#505050', rgba: 0x505050FF });\n        assert.deepStrictEqual(channels.toColor(0x60, 0x60, 0x60), { css: '#606060', rgba: 0x606060FF });\n        assert.deepStrictEqual(channels.toColor(0x70, 0x70, 0x70), { css: '#707070', rgba: 0x707070FF });\n        assert.deepStrictEqual(channels.toColor(0x80, 0x80, 0x80), { css: '#808080', rgba: 0x808080FF });\n        assert.deepStrictEqual(channels.toColor(0x90, 0x90, 0x90), { css: '#909090', rgba: 0x909090FF });\n        assert.deepStrictEqual(channels.toColor(0xa0, 0xa0, 0xa0), { css: '#a0a0a0', rgba: 0xa0a0a0FF });\n        assert.deepStrictEqual(channels.toColor(0xb0, 0xb0, 0xb0), { css: '#b0b0b0', rgba: 0xb0b0b0FF });\n        assert.deepStrictEqual(channels.toColor(0xc0, 0xc0, 0xc0), { css: '#c0c0c0', rgba: 0xc0c0c0FF });\n        assert.deepStrictEqual(channels.toColor(0xd0, 0xd0, 0xd0), { css: '#d0d0d0', rgba: 0xd0d0d0FF });\n        assert.deepStrictEqual(channels.toColor(0xe0, 0xe0, 0xe0), { css: '#e0e0e0', rgba: 0xe0e0e0FF });\n        assert.deepStrictEqual(channels.toColor(0xf0, 0xf0, 0xf0), { css: '#f0f0f0', rgba: 0xf0f0f0FF });\n        assert.deepStrictEqual(channels.toColor(0xff, 0xff, 0xff), { css: '#ffffff', rgba: 0xffffffFF });\n      });\n      it('should convert an rgba array to an IColor', () => {\n        assert.deepStrictEqual(channels.toColor(0x00, 0x00, 0x00, 0x00), { css: '#00000000', rgba: 0x00000000 });\n        assert.deepStrictEqual(channels.toColor(0x10, 0x10, 0x10, 0x10), { css: '#10101010', rgba: 0x10101010 });\n        assert.deepStrictEqual(channels.toColor(0x20, 0x20, 0x20, 0x20), { css: '#20202020', rgba: 0x20202020 });\n        assert.deepStrictEqual(channels.toColor(0x30, 0x30, 0x30, 0x30), { css: '#30303030', rgba: 0x30303030 });\n        assert.deepStrictEqual(channels.toColor(0x40, 0x40, 0x40, 0x40), { css: '#40404040', rgba: 0x40404040 });\n        assert.deepStrictEqual(channels.toColor(0x50, 0x50, 0x50, 0x50), { css: '#50505050', rgba: 0x50505050 });\n        assert.deepStrictEqual(channels.toColor(0x60, 0x60, 0x60, 0x60), { css: '#60606060', rgba: 0x60606060 });\n        assert.deepStrictEqual(channels.toColor(0x70, 0x70, 0x70, 0x70), { css: '#70707070', rgba: 0x70707070 });\n        assert.deepStrictEqual(channels.toColor(0x80, 0x80, 0x80, 0x80), { css: '#80808080', rgba: 0x80808080 });\n        assert.deepStrictEqual(channels.toColor(0x90, 0x90, 0x90, 0x90), { css: '#90909090', rgba: 0x90909090 });\n        assert.deepStrictEqual(channels.toColor(0xa0, 0xa0, 0xa0, 0xa0), { css: '#a0a0a0a0', rgba: 0xa0a0a0a0 });\n        assert.deepStrictEqual(channels.toColor(0xb0, 0xb0, 0xb0, 0xb0), { css: '#b0b0b0b0', rgba: 0xb0b0b0b0 });\n        assert.deepStrictEqual(channels.toColor(0xc0, 0xc0, 0xc0, 0xc0), { css: '#c0c0c0c0', rgba: 0xc0c0c0c0 });\n        assert.deepStrictEqual(channels.toColor(0xd0, 0xd0, 0xd0, 0xd0), { css: '#d0d0d0d0', rgba: 0xd0d0d0d0 });\n        assert.deepStrictEqual(channels.toColor(0xe0, 0xe0, 0xe0, 0xe0), { css: '#e0e0e0e0', rgba: 0xe0e0e0e0 });\n        assert.deepStrictEqual(channels.toColor(0xf0, 0xf0, 0xf0, 0xf0), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 });\n        assert.deepStrictEqual(channels.toColor(0xff, 0xff, 0xff, 0xff), { css: '#ffffffff', rgba: 0xffffffff });\n      });\n    });\n  });\n\n  describe('color', () => {\n    describe('blend', () => {\n      it('should blend colors based on the alpha channel', () => {\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });\n        assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });\n      });\n    });\n\n    describe('opaque', () => {\n      it('should make the color opaque', () => {\n        assert.deepEqual(color.opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF });\n        assert.deepEqual(color.opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF });\n        assert.deepEqual(color.opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF });\n        assert.deepEqual(color.opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF });\n        assert.deepEqual(color.opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF });\n        assert.deepEqual(color.opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF });\n        assert.deepEqual(color.opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF });\n        assert.deepEqual(color.opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF });\n        assert.deepEqual(color.opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF });\n        assert.deepEqual(color.opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF });\n        assert.deepEqual(color.opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF });\n        assert.deepEqual(color.opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF });\n        assert.deepEqual(color.opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF });\n        assert.deepEqual(color.opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF });\n        assert.deepEqual(color.opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF });\n        assert.deepEqual(color.opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF });\n        assert.deepEqual(color.opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });\n      });\n    });\n\n    describe('isOpaque', () => {\n      it('should return true for opaque colors', () => {\n        assert.ok(color.isOpaque(css.toColor('#000000')));\n        assert.ok(color.isOpaque(css.toColor('#000000ff')));\n        assert.ok(color.isOpaque(css.toColor('#808080')));\n        assert.ok(color.isOpaque(css.toColor('#808080ff')));\n        assert.ok(color.isOpaque(css.toColor('#ffffff')));\n        assert.ok(color.isOpaque(css.toColor('#ffffffff')));\n      });\n      it('should return false for transparent colors', () => {\n        assert.ok(!color.isOpaque(css.toColor('#00000000')));\n        assert.ok(!color.isOpaque(css.toColor('#00000080')));\n        assert.ok(!color.isOpaque(css.toColor('#000000fe')));\n        assert.ok(!color.isOpaque(css.toColor('#80808000')));\n        assert.ok(!color.isOpaque(css.toColor('#80808080')));\n        assert.ok(!color.isOpaque(css.toColor('#808080fe')));\n        assert.ok(!color.isOpaque(css.toColor('#ffffff00')));\n        assert.ok(!color.isOpaque(css.toColor('#ffffff80')));\n        assert.ok(!color.isOpaque(css.toColor('#fffffffe')));\n      });\n    });\n\n    describe('opacity', () => {\n      it('should make the color transparent', () => {\n        assert.deepEqual(color.opacity(css.toColor('#000000'), 0), { css: '#00000000', rgba: 0x00000000 });\n        assert.deepEqual(color.opacity(css.toColor('#000000'), 0.25), { css: '#00000040', rgba: 0x00000040 });\n        assert.deepEqual(color.opacity(css.toColor('#000000'), 0.5), { css: '#00000080', rgba: 0x00000080 });\n        assert.deepEqual(color.opacity(css.toColor('#000000'), 0.75), { css: '#000000bf', rgba: 0x000000bf });\n        assert.deepEqual(color.opacity(css.toColor('#000000'), 1), { css: '#000000ff', rgba: 0x000000ff });\n      });\n    });\n  });\n\n  describe('css', () => {\n    describe('toColor', () => {\n      it('should convert the #rgb format to an IColor', () => {\n        assert.deepEqual(css.toColor('#000'), { css: '#000000', rgba: 0x000000FF });\n        assert.deepEqual(css.toColor('#111'), { css: '#111111', rgba: 0x111111FF });\n        assert.deepEqual(css.toColor('#222'), { css: '#222222', rgba: 0x222222FF });\n        assert.deepEqual(css.toColor('#333'), { css: '#333333', rgba: 0x333333FF });\n        assert.deepEqual(css.toColor('#444'), { css: '#444444', rgba: 0x444444FF });\n        assert.deepEqual(css.toColor('#555'), { css: '#555555', rgba: 0x555555FF });\n        assert.deepEqual(css.toColor('#666'), { css: '#666666', rgba: 0x666666FF });\n        assert.deepEqual(css.toColor('#777'), { css: '#777777', rgba: 0x777777FF });\n        assert.deepEqual(css.toColor('#888'), { css: '#888888', rgba: 0x888888FF });\n        assert.deepEqual(css.toColor('#999'), { css: '#999999', rgba: 0x999999FF });\n        assert.deepEqual(css.toColor('#aaa'), { css: '#aaaaaa', rgba: 0xaaaaaaFF });\n        assert.deepEqual(css.toColor('#bbb'), { css: '#bbbbbb', rgba: 0xbbbbbbFF });\n        assert.deepEqual(css.toColor('#ccc'), { css: '#cccccc', rgba: 0xccccccFF });\n        assert.deepEqual(css.toColor('#ddd'), { css: '#dddddd', rgba: 0xddddddFF });\n        assert.deepEqual(css.toColor('#eee'), { css: '#eeeeee', rgba: 0xeeeeeeFF });\n        assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF });\n        assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF });\n      });\n      it('should convert the #rgb format to an IColor', () => {\n        assert.deepEqual(css.toColor('#0000'), { css: '#00000000', rgba: 0x00000000 });\n        assert.deepEqual(css.toColor('#1111'), { css: '#11111111', rgba: 0x11111111 });\n        assert.deepEqual(css.toColor('#2222'), { css: '#22222222', rgba: 0x22222222 });\n        assert.deepEqual(css.toColor('#3333'), { css: '#33333333', rgba: 0x33333333 });\n        assert.deepEqual(css.toColor('#4444'), { css: '#44444444', rgba: 0x44444444 });\n        assert.deepEqual(css.toColor('#5555'), { css: '#55555555', rgba: 0x55555555 });\n        assert.deepEqual(css.toColor('#6666'), { css: '#66666666', rgba: 0x66666666 });\n        assert.deepEqual(css.toColor('#7777'), { css: '#77777777', rgba: 0x77777777 });\n        assert.deepEqual(css.toColor('#8888'), { css: '#88888888', rgba: 0x88888888 });\n        assert.deepEqual(css.toColor('#9999'), { css: '#99999999', rgba: 0x99999999 });\n        assert.deepEqual(css.toColor('#aaaa'), { css: '#aaaaaaaa', rgba: 0xaaaaaaaa });\n        assert.deepEqual(css.toColor('#bbbb'), { css: '#bbbbbbbb', rgba: 0xbbbbbbbb });\n        assert.deepEqual(css.toColor('#cccc'), { css: '#cccccccc', rgba: 0xcccccccc });\n        assert.deepEqual(css.toColor('#dddd'), { css: '#dddddddd', rgba: 0xdddddddd });\n        assert.deepEqual(css.toColor('#eeee'), { css: '#eeeeeeee', rgba: 0xeeeeeeee });\n        assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff });\n        assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff });\n      });\n      it('should convert the #rrggbb format to an IColor', () => {\n        assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF });\n        assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF });\n        assert.deepEqual(css.toColor('#202020'), { css: '#202020', rgba: 0x202020FF });\n        assert.deepEqual(css.toColor('#303030'), { css: '#303030', rgba: 0x303030FF });\n        assert.deepEqual(css.toColor('#404040'), { css: '#404040', rgba: 0x404040FF });\n        assert.deepEqual(css.toColor('#505050'), { css: '#505050', rgba: 0x505050FF });\n        assert.deepEqual(css.toColor('#606060'), { css: '#606060', rgba: 0x606060FF });\n        assert.deepEqual(css.toColor('#707070'), { css: '#707070', rgba: 0x707070FF });\n        assert.deepEqual(css.toColor('#808080'), { css: '#808080', rgba: 0x808080FF });\n        assert.deepEqual(css.toColor('#909090'), { css: '#909090', rgba: 0x909090FF });\n        assert.deepEqual(css.toColor('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });\n        assert.deepEqual(css.toColor('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });\n        assert.deepEqual(css.toColor('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });\n        assert.deepEqual(css.toColor('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });\n        assert.deepEqual(css.toColor('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });\n        assert.deepEqual(css.toColor('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });\n        assert.deepEqual(css.toColor('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });\n      });\n      it('should convert the #rrggbbaa format to an IColor', () => {\n        assert.deepEqual(css.toColor('#00000000'), { css: '#00000000', rgba: 0x00000000 });\n        assert.deepEqual(css.toColor('#10101010'), { css: '#10101010', rgba: 0x10101010 });\n        assert.deepEqual(css.toColor('#20202020'), { css: '#20202020', rgba: 0x20202020 });\n        assert.deepEqual(css.toColor('#30303030'), { css: '#30303030', rgba: 0x30303030 });\n        assert.deepEqual(css.toColor('#40404040'), { css: '#40404040', rgba: 0x40404040 });\n        assert.deepEqual(css.toColor('#50505050'), { css: '#50505050', rgba: 0x50505050 });\n        assert.deepEqual(css.toColor('#60606060'), { css: '#60606060', rgba: 0x60606060 });\n        assert.deepEqual(css.toColor('#70707070'), { css: '#70707070', rgba: 0x70707070 });\n        assert.deepEqual(css.toColor('#80808080'), { css: '#80808080', rgba: 0x80808080 });\n        assert.deepEqual(css.toColor('#90909090'), { css: '#90909090', rgba: 0x90909090 });\n        assert.deepEqual(css.toColor('#a0a0a0a0'), { css: '#a0a0a0a0', rgba: 0xa0a0a0a0 });\n        assert.deepEqual(css.toColor('#b0b0b0b0'), { css: '#b0b0b0b0', rgba: 0xb0b0b0b0 });\n        assert.deepEqual(css.toColor('#c0c0c0c0'), { css: '#c0c0c0c0', rgba: 0xc0c0c0c0 });\n        assert.deepEqual(css.toColor('#d0d0d0d0'), { css: '#d0d0d0d0', rgba: 0xd0d0d0d0 });\n        assert.deepEqual(css.toColor('#e0e0e0e0'), { css: '#e0e0e0e0', rgba: 0xe0e0e0e0 });\n        assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 });\n        assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff });\n      });\n      it('should convert the rgb() format to an IColor', () => {\n        assert.deepEqual(css.toColor('rgb(0, 0, 0)'), { css: '#000000ff', rgba: 0x000000ff });\n        assert.deepEqual(css.toColor('rgb(80, 0, 0)'), { css: '#500000ff', rgba: 0x500000ff });\n        assert.deepEqual(css.toColor('rgb(0, 80, 0)'), { css: '#005000ff', rgba: 0x005000ff });\n        assert.deepEqual(css.toColor('rgb(0, 0, 80)'), { css: '#000050ff', rgba: 0x000050ff });\n        assert.deepEqual(css.toColor('rgb(255, 255, 255)'), { css: '#ffffffff', rgba: 0xffffffff });\n      });\n      it('should convert the rgba() format to an IColor', () => {\n        assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: '#00000000', rgba: 0x00000000 });\n        assert.deepEqual(css.toColor('rgba(80, 0, 0, 0.5)'), { css: '#50000080', rgba: 0x50000080 });\n        assert.deepEqual(css.toColor('rgba(0, 80, 0, 0.5)'), { css: '#00500080', rgba: 0x00500080 });\n        assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: '#00005080', rgba: 0x00005080 });\n        assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: '#ffffffff', rgba: 0xffffffff });\n      });\n      it('should convert \"transparent\" to an IColor', () => {\n        assert.deepEqual(css.toColor('transparent'), { css: 'transparent', rgba: 0x00000000 });\n      });\n    });\n  });\n\n  describe('rgb', () => {\n    describe('relativeLuminance', () => {\n      it('should calculate the relative luminance of the color', () => {\n        assert.equal(rgb.relativeLuminance(0x000000), 0);\n        assert.equal(rgb.relativeLuminance(0x101010).toFixed(4), '0.0052');\n        assert.equal(rgb.relativeLuminance(0x202020).toFixed(4), '0.0144');\n        assert.equal(rgb.relativeLuminance(0x303030).toFixed(4), '0.0296');\n        assert.equal(rgb.relativeLuminance(0x404040).toFixed(4), '0.0513');\n        assert.equal(rgb.relativeLuminance(0x505050).toFixed(4), '0.0802');\n        assert.equal(rgb.relativeLuminance(0x606060).toFixed(4), '0.1170');\n        assert.equal(rgb.relativeLuminance(0x707070).toFixed(4), '0.1620');\n        assert.equal(rgb.relativeLuminance(0x808080).toFixed(4), '0.2159');\n        assert.equal(rgb.relativeLuminance(0x909090).toFixed(4), '0.2789');\n        assert.equal(rgb.relativeLuminance(0xA0A0A0).toFixed(4), '0.3515');\n        assert.equal(rgb.relativeLuminance(0xB0B0B0).toFixed(4), '0.4342');\n        assert.equal(rgb.relativeLuminance(0xC0C0C0).toFixed(4), '0.5271');\n        assert.equal(rgb.relativeLuminance(0xD0D0D0).toFixed(4), '0.6308');\n        assert.equal(rgb.relativeLuminance(0xE0E0E0).toFixed(4), '0.7454');\n        assert.equal(rgb.relativeLuminance(0xF0F0F0).toFixed(4), '0.8714');\n        assert.equal(rgb.relativeLuminance(0xFFFFFF), 1);\n      });\n    });\n  });\n\n  describe('rgba', () => {\n    describe('blend', () => {\n      it('should blend colors based on the alpha channel', () => {\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF00), 0x000000FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF10), 0x101010FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF20), 0x202020FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF30), 0x303030FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF40), 0x404040FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF50), 0x505050FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF60), 0x606060FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF70), 0x707070FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF80), 0x808080FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF90), 0x909090FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFA0), 0xA0A0A0FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFB0), 0xB0B0B0FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFC0), 0xC0C0C0FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFD0), 0xD0D0D0FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFE0), 0xE0E0E0FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFF0), 0xF0F0F0FF);\n        assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFFF), 0xFFFFFFFF);\n      });\n    });\n    describe('ensureContrastRatio', () => {\n      it('should return undefined if the color already meets the contrast ratio (black bg)', () => {\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 1), undefined);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 2), undefined);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 3), undefined);\n      });\n      it('should return a color that meets the contrast ratio (black bg)', () => {\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 4), 0x707070ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 5), 0x7f7f7fff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 6), 0x8c8c8cff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 7), 0x989898ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 9), 0xadadadff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 11), 0xbebebeff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 15), 0xdbdbdbff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 18), 0xeeeeeeff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 20), 0xfafafaff);\n        assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 21), 0xffffffff);\n      });\n      it('should return undefined if the color already meets the contrast ratio (white bg)', () => {\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 1), undefined);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 2), undefined);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 3), undefined);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 4), undefined);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 5), undefined);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 6), undefined);\n      });\n      it('should return a color that meets the contrast ratio (white bg)', () => {\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 7), 0x565656ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 8), 0x4d4d4dff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 9), 0x454545ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 10), 0x3e3e3eff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 11), 0x373737ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 12), 0x313131ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 13), 0x313131ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 14), 0x272727ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 15), 0x232323ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 16), 0x1f1f1fff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 17), 0x1b1b1bff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 18), 0x151515ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 19), 0x101010ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 20), 0x080808ff);\n        assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 21), 0x000000ff);\n      });\n    });\n\n    describe('toChannels', () => {\n      it('should convert an rgba number to an rgba array', () => {\n        assert.deepEqual(rgba.toChannels(0x00000000), [0x00, 0x00, 0x00, 0x00]);\n        assert.deepEqual(rgba.toChannels(0x10101010), [0x10, 0x10, 0x10, 0x10]);\n        assert.deepEqual(rgba.toChannels(0x20202020), [0x20, 0x20, 0x20, 0x20]);\n        assert.deepEqual(rgba.toChannels(0x30303030), [0x30, 0x30, 0x30, 0x30]);\n        assert.deepEqual(rgba.toChannels(0x40404040), [0x40, 0x40, 0x40, 0x40]);\n        assert.deepEqual(rgba.toChannels(0x50505050), [0x50, 0x50, 0x50, 0x50]);\n        assert.deepEqual(rgba.toChannels(0x60606060), [0x60, 0x60, 0x60, 0x60]);\n        assert.deepEqual(rgba.toChannels(0x70707070), [0x70, 0x70, 0x70, 0x70]);\n        assert.deepEqual(rgba.toChannels(0x80808080), [0x80, 0x80, 0x80, 0x80]);\n        assert.deepEqual(rgba.toChannels(0x90909090), [0x90, 0x90, 0x90, 0x90]);\n        assert.deepEqual(rgba.toChannels(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]);\n        assert.deepEqual(rgba.toChannels(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]);\n        assert.deepEqual(rgba.toChannels(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]);\n        assert.deepEqual(rgba.toChannels(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]);\n        assert.deepEqual(rgba.toChannels(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]);\n        assert.deepEqual(rgba.toChannels(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]);\n        assert.deepEqual(rgba.toChannels(0xffffffff), [0xff, 0xff, 0xff, 0xff]);\n      });\n    });\n  });\n\n  describe('toPaddedHex', () => {\n    it('should convert numbers to 2-digit hex values', () => {\n      assert.equal(toPaddedHex(0x00), '00');\n      assert.equal(toPaddedHex(0x10), '10');\n      assert.equal(toPaddedHex(0x20), '20');\n      assert.equal(toPaddedHex(0x30), '30');\n      assert.equal(toPaddedHex(0x40), '40');\n      assert.equal(toPaddedHex(0x50), '50');\n      assert.equal(toPaddedHex(0x60), '60');\n      assert.equal(toPaddedHex(0x70), '70');\n      assert.equal(toPaddedHex(0x80), '80');\n      assert.equal(toPaddedHex(0x90), '90');\n      assert.equal(toPaddedHex(0xa0), 'a0');\n      assert.equal(toPaddedHex(0xb0), 'b0');\n      assert.equal(toPaddedHex(0xc0), 'c0');\n      assert.equal(toPaddedHex(0xd0), 'd0');\n      assert.equal(toPaddedHex(0xe0), 'e0');\n      assert.equal(toPaddedHex(0xf0), 'f0');\n      assert.equal(toPaddedHex(0xff), 'ff');\n    });\n  });\n\n  describe('contrastRatio', () => {\n    it('should calculate the relative luminance of the color', () => {\n      assert.equal(contrastRatio(0, 0), 1);\n      assert.equal(contrastRatio(0, 0.5), 11);\n      assert.equal(contrastRatio(0, 1), 21);\n    });\n    it('should work regardless of the parameter order', () => {\n      assert.equal(contrastRatio(0, 1), 21);\n      assert.equal(contrastRatio(1, 0), 21);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/Color.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColor, IColorRGB } from 'common/Types';\n\nlet $r = 0;\nlet $g = 0;\nlet $b = 0;\nlet $a = 0;\n\nexport const NULL_COLOR: IColor = {\n  css: '#00000000',\n  rgba: 0\n};\n\n/**\n * Helper functions where the source type is \"channels\" (individual color channels as numbers).\n */\nexport namespace channels {\n  export function toCss(r: number, g: number, b: number, a?: number): string {\n    if (a !== undefined) {\n      return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`;\n    }\n    return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`;\n  }\n\n  export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {\n    // Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32\n    // on LE systems, before it can be used for direct 32-bit buffer writes.\n    // >>> 0 forces an unsigned int\n    return (r << 24 | g << 16 | b << 8 | a) >>> 0;\n  }\n\n  export function toColor(r: number, g: number, b: number, a?: number): IColor {\n    return {\n      css: channels.toCss(r, g, b, a),\n      rgba: channels.toRgba(r, g, b, a)\n    };\n  }\n}\n\n/**\n * Helper functions where the source type is `IColor`.\n */\nexport namespace color {\n  export function blend(bg: IColor, fg: IColor): IColor {\n    $a = (fg.rgba & 0xFF) / 255;\n    if ($a === 1) {\n      return {\n        css: fg.css,\n        rgba: fg.rgba\n      };\n    }\n    const fgR = (fg.rgba >> 24) & 0xFF;\n    const fgG = (fg.rgba >> 16) & 0xFF;\n    const fgB = (fg.rgba >> 8) & 0xFF;\n    const bgR = (bg.rgba >> 24) & 0xFF;\n    const bgG = (bg.rgba >> 16) & 0xFF;\n    const bgB = (bg.rgba >> 8) & 0xFF;\n    $r = bgR + Math.round((fgR - bgR) * $a);\n    $g = bgG + Math.round((fgG - bgG) * $a);\n    $b = bgB + Math.round((fgB - bgB) * $a);\n    const css = channels.toCss($r, $g, $b);\n    const rgba = channels.toRgba($r, $g, $b);\n    return { css, rgba };\n  }\n\n  export function isOpaque(color: IColor): boolean {\n    return (color.rgba & 0xFF) === 0xFF;\n  }\n\n  export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined {\n    const result = rgba.ensureContrastRatio(bg.rgba, fg.rgba, ratio);\n    if (!result) {\n      return undefined;\n    }\n    return channels.toColor(\n      (result >> 24 & 0xFF),\n      (result >> 16 & 0xFF),\n      (result >> 8  & 0xFF)\n    );\n  }\n\n  export function opaque(color: IColor): IColor {\n    const rgbaColor = (color.rgba | 0xFF) >>> 0;\n    [$r, $g, $b] = rgba.toChannels(rgbaColor);\n    return {\n      css: channels.toCss($r, $g, $b),\n      rgba: rgbaColor\n    };\n  }\n\n  export function opacity(color: IColor, opacity: number): IColor {\n    $a = Math.round(opacity * 0xFF);\n    [$r, $g, $b] = rgba.toChannels(color.rgba);\n    return {\n      css: channels.toCss($r, $g, $b, $a),\n      rgba: channels.toRgba($r, $g, $b, $a)\n    };\n  }\n\n  export function multiplyOpacity(color: IColor, factor: number): IColor {\n    $a = color.rgba & 0xFF;\n    return opacity(color, ($a * factor) / 0xFF);\n  }\n\n  export function toColorRGB(color: IColor): IColorRGB {\n    return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF];\n  }\n}\n\n/**\n * Helper functions where the source type is \"css\" (string: '#rgb', '#rgba', '#rrggbb',\n * '#rrggbbaa').\n */\nexport namespace css {\n  // Attempt to set get the shared canvas context\n  let $ctx: CanvasRenderingContext2D | undefined;\n  let $litmusColor: CanvasGradient | undefined;\n  try {\n    // This is guaranteed to run in the first window, so document should be correct\n    const canvas = document.createElement('canvas');\n    canvas.width = 1;\n    canvas.height = 1;\n    const ctx = canvas.getContext('2d', {\n      willReadFrequently: true\n    });\n    if (ctx) {\n      $ctx = ctx;\n      $ctx.globalCompositeOperation = 'copy';\n      $litmusColor = $ctx.createLinearGradient(0, 0, 1, 1);\n    }\n  }\n  catch {\n    // noop\n  }\n\n  /**\n   * Converts a css string to an IColor, this should handle all valid CSS color strings and will\n   * throw if it's invalid. The ideal format to use is `#rrggbb[aa]` as it's the fastest to parse.\n   *\n   * Only `#rgb[a]`, `#rrggbb[aa]`, `rgb()` and `rgba()` formats are supported when run in a Node\n   * environment.\n   */\n  export function toColor(css: string): IColor {\n    // Formats: #rgb[a] and #rrggbb[aa]\n    if (css.match(/#[\\da-f]{3,8}/i)) {\n      switch (css.length) {\n        case 4: { // #rgb\n          $r = parseInt(css.slice(1, 2).repeat(2), 16);\n          $g = parseInt(css.slice(2, 3).repeat(2), 16);\n          $b = parseInt(css.slice(3, 4).repeat(2), 16);\n          return channels.toColor($r, $g, $b);\n        }\n        case 5: { // #rgba\n          $r = parseInt(css.slice(1, 2).repeat(2), 16);\n          $g = parseInt(css.slice(2, 3).repeat(2), 16);\n          $b = parseInt(css.slice(3, 4).repeat(2), 16);\n          $a = parseInt(css.slice(4, 5).repeat(2), 16);\n          return channels.toColor($r, $g, $b, $a);\n        }\n        case 7: // #rrggbb\n          return {\n            css,\n            rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0\n          };\n        case 9: // #rrggbbaa\n          return {\n            css,\n            rgba: parseInt(css.slice(1), 16) >>> 0\n          };\n      }\n    }\n\n    // Formats: rgb() or rgba()\n    const rgbaMatch = css.match(/rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*(,\\s*(0|1|\\d?\\.(\\d+))\\s*)?\\)/);\n    if (rgbaMatch) {\n      $r = parseInt(rgbaMatch[1]);\n      $g = parseInt(rgbaMatch[2]);\n      $b = parseInt(rgbaMatch[3]);\n      $a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);\n      return channels.toColor($r, $g, $b, $a);\n    }\n\n    // Handle the \"transparent\" keyword\n    if (css === 'transparent') {\n      return {\n        css: 'transparent',\n        rgba: 0x00000000\n      };\n    }\n\n    // Validate the context is available for canvas-based color parsing\n    if (!$ctx || !$litmusColor) {\n      throw new Error('css.toColor: Unsupported css format');\n    }\n\n    // Validate the color using canvas fillStyle\n    // See https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles\n    $ctx.fillStyle = $litmusColor;\n    $ctx.fillStyle = css;\n    if (typeof $ctx.fillStyle !== 'string') {\n      throw new Error('css.toColor: Unsupported css format');\n    }\n\n    $ctx.fillRect(0, 0, 1, 1);\n    [$r, $g, $b, $a] = $ctx.getImageData(0, 0, 1, 1).data;\n\n    // Validate the color is non-transparent as color hue gets lost when drawn to the canvas\n    if ($a !== 0xFF) {\n      throw new Error('css.toColor: Unsupported css format');\n    }\n\n    // Extract the color from the canvas' fillStyle property which exposes the color value in rgba()\n    // format\n    // See https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color\n    return {\n      rgba: channels.toRgba($r, $g, $b, $a),\n      css\n    };\n  }\n}\n\n/**\n * Helper functions where the source type is \"rgb\" (number: 0xrrggbb).\n */\nexport namespace rgb {\n  /**\n   * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio\n   * between two colors.\n   * @param rgb The color to use.\n   * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef\n   */\n  export function relativeLuminance(rgb: number): number {\n    return relativeLuminance2(\n      (rgb >> 16) & 0xFF,\n      (rgb >> 8 ) & 0xFF,\n      (rgb      ) & 0xFF);\n  }\n\n  /**\n   * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio\n   * between two colors.\n   * @param r The red channel (0x00 to 0xFF).\n   * @param g The green channel (0x00 to 0xFF).\n   * @param b The blue channel (0x00 to 0xFF).\n   * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef\n   */\n  export function relativeLuminance2(r: number, g: number, b: number): number {\n    const rs = r / 255;\n    const gs = g / 255;\n    const bs = b / 255;\n    const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);\n    const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);\n    const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);\n    return rr * 0.2126 + rg * 0.7152 + rb * 0.0722;\n  }\n}\n\n/**\n * Helper functions where the source type is \"rgba\" (number: 0xrrggbbaa).\n */\nexport namespace rgba {\n  export function blend(bg: number, fg: number): number {\n    $a = (fg & 0xFF) / 0xFF;\n    if ($a === 1) {\n      return fg;\n    }\n    const fgR = (fg >> 24) & 0xFF;\n    const fgG = (fg >> 16) & 0xFF;\n    const fgB = (fg >> 8) & 0xFF;\n    const bgR = (bg >> 24) & 0xFF;\n    const bgG = (bg >> 16) & 0xFF;\n    const bgB = (bg >> 8) & 0xFF;\n    $r = bgR + Math.round((fgR - bgR) * $a);\n    $g = bgG + Math.round((fgG - bgG) * $a);\n    $b = bgB + Math.round((fgB - bgB) * $a);\n    return channels.toRgba($r, $g, $b);\n  }\n\n  /**\n   * Given a foreground color and a background color, either increase or reduce the luminance of the\n   * foreground color until the specified contrast ratio is met. If pure white or black is hit\n   * without the contrast ratio being met, go the other direction using the background color as the\n   * foreground color and take either the first or second result depending on which has the higher\n   * contrast ratio.\n   *\n   * `undefined` will be returned if the contrast ratio is already met.\n   *\n   * @param bgRgba The background color in rgba format.\n   * @param fgRgba The foreground color in rgba format.\n   * @param ratio The contrast ratio to achieve.\n   */\n  export function ensureContrastRatio(bgRgba: number, fgRgba: number, ratio: number): number | undefined {\n    const bgL = rgb.relativeLuminance(bgRgba >> 8);\n    const fgL = rgb.relativeLuminance(fgRgba >> 8);\n    const cr = contrastRatio(bgL, fgL);\n    if (cr < ratio) {\n      if (fgL < bgL) {\n        const resultA = reduceLuminance(bgRgba, fgRgba, ratio);\n        const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8));\n        if (resultARatio < ratio) {\n          const resultB = increaseLuminance(bgRgba, fgRgba, ratio);\n          const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8));\n          return resultARatio > resultBRatio ? resultA : resultB;\n        }\n        return resultA;\n      }\n      const resultA = increaseLuminance(bgRgba, fgRgba, ratio);\n      const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8));\n      if (resultARatio < ratio) {\n        const resultB = reduceLuminance(bgRgba, fgRgba, ratio);\n        const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8));\n        return resultARatio > resultBRatio ? resultA : resultB;\n      }\n      return resultA;\n    }\n    return undefined;\n  }\n\n  export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number {\n    // This is a naive but fast approach to reducing luminance as converting to\n    // HSL and back is expensive\n    const bgR = (bgRgba >> 24) & 0xFF;\n    const bgG = (bgRgba >> 16) & 0xFF;\n    const bgB = (bgRgba >>  8) & 0xFF;\n    let fgR = (fgRgba >> 24) & 0xFF;\n    let fgG = (fgRgba >> 16) & 0xFF;\n    let fgB = (fgRgba >>  8) & 0xFF;\n    let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));\n    while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) {\n      // Reduce by 10% until the ratio is hit\n      fgR -= Math.max(0, Math.ceil(fgR * 0.1));\n      fgG -= Math.max(0, Math.ceil(fgG * 0.1));\n      fgB -= Math.max(0, Math.ceil(fgB * 0.1));\n      cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));\n    }\n    return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;\n  }\n\n  export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number {\n    // This is a naive but fast approach to increasing luminance as converting to\n    // HSL and back is expensive\n    const bgR = (bgRgba >> 24) & 0xFF;\n    const bgG = (bgRgba >> 16) & 0xFF;\n    const bgB = (bgRgba >>  8) & 0xFF;\n    let fgR = (fgRgba >> 24) & 0xFF;\n    let fgG = (fgRgba >> 16) & 0xFF;\n    let fgB = (fgRgba >>  8) & 0xFF;\n    let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));\n    while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) {\n      // Increase by 10% until the ratio is hit\n      fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1));\n      fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1));\n      fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1));\n      cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));\n    }\n    return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;\n  }\n\n  export function toChannels(value: number): [number, number, number, number] {\n    return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];\n  }\n}\n\nexport function toPaddedHex(c: number): string {\n  const s = c.toString(16);\n  return s.length < 2 ? '0' + s : s;\n}\n\n/**\n * Gets the contrast ratio between two relative luminance values.\n * @param l1 The first relative luminance.\n * @param l2 The first relative luminance.\n * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef\n */\nexport function contrastRatio(l1: number, l2: number): number {\n  if (l1 < l2) {\n    return (l2 + 0.05) / (l1 + 0.05);\n  }\n  return (l1 + 0.05) / (l2 + 0.05);\n}\n"
  },
  {
    "path": "src/common/CoreTerminal.ts",
    "content": "/**\n * Copyright (c) 2014-2020 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n *   Fabrice Bellard's javascript vt100 for jslinux:\n *   http://bellard.org/jslinux/\n *   Copyright (c) 2011 Fabrice Bellard\n *   The original design remains. The terminal itself\n *   has been extended to include xterm CSI codes, among\n *   other features.\n *\n * Terminal Emulation References:\n *   http://vt100.net/\n *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n *   http://invisible-island.net/vttest/\n *   http://www.inwap.com/pdp10/ansicode.txt\n *   http://linux.die.net/man/4/console_codes\n *   http://linux.die.net/man/7/urxvt\n */\n\nimport { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services';\nimport { InstantiationService } from 'common/services/InstantiationService';\nimport { LogService } from 'common/services/LogService';\nimport { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';\nimport { OptionsService } from 'common/services/OptionsService';\nimport { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent } from 'common/Types';\nimport { CoreService } from 'common/services/CoreService';\nimport { MouseStateService } from 'common/services/MouseStateService';\nimport { UnicodeService } from 'common/services/UnicodeService';\nimport { CharsetService } from 'common/services/CharsetService';\nimport { updateWindowsModeWrappedState } from 'common/WindowsMode';\nimport { IFunctionIdentifier, IParams } from 'common/parser/Types';\nimport { IBufferSet } from 'common/buffer/Types';\nimport { InputHandler } from 'common/InputHandler';\nimport { WriteBuffer } from 'common/input/WriteBuffer';\nimport { OscLinkService } from 'common/services/OscLinkService';\nimport { Emitter, EventUtils, type IEvent } from 'common/Event';\nimport { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';\n\n// Only trigger this warning a single time per session\nlet hasWriteSyncWarnHappened = false;\n\nexport abstract class CoreTerminal extends Disposable implements ICoreTerminal {\n  protected readonly _instantiationService: IInstantiationService;\n  protected readonly _bufferService: IBufferService;\n  protected readonly _logService: ILogService;\n  protected readonly _charsetService: ICharsetService;\n  protected readonly _oscLinkService: IOscLinkService;\n\n  public readonly mouseStateService: IMouseStateService;\n  public readonly coreService: ICoreService;\n  public readonly unicodeService: IUnicodeService;\n  public readonly optionsService: IOptionsService;\n\n  protected _inputHandler: InputHandler;\n  private _writeBuffer: WriteBuffer;\n  private _windowsWrappingHeuristics = this._register(new MutableDisposable());\n\n  private readonly _onBinary = this._register(new Emitter<string>());\n  public readonly onBinary = this._onBinary.event;\n  private readonly _onData = this._register(new Emitter<string>());\n  public readonly onData = this._onData.event;\n  protected _onLineFeed = this._register(new Emitter<void>());\n  public readonly onLineFeed = this._onLineFeed.event;\n  protected readonly _onRender = this._register(new Emitter<{ start: number, end: number }>());\n  public readonly onRender = this._onRender.event;\n  private readonly _onResize = this._register(new Emitter<{ cols: number, rows: number }>());\n  public readonly onResize = this._onResize.event;\n  protected readonly _onWriteParsed = this._register(new Emitter<void>());\n  public readonly onWriteParsed = this._onWriteParsed.event;\n\n  /**\n   * Internally we track the source of the scroll but this is meaningless outside the library so\n   * it's filtered out.\n   */\n  protected _onScrollApi?: Emitter<number>;\n  protected _onScroll = this._register(new Emitter<IScrollEvent>());\n  public get onScroll(): IEvent<number> {\n    if (!this._onScrollApi) {\n      this._onScrollApi = this._register(new Emitter<number>());\n      this._onScroll.event(ev => {\n        this._onScrollApi?.fire(ev.position);\n      });\n    }\n    return this._onScrollApi.event;\n  }\n\n  public get cols(): number { return this._bufferService.cols; }\n  public get rows(): number { return this._bufferService.rows; }\n  public get buffers(): IBufferSet { return this._bufferService.buffers; }\n  public get options(): Required<ITerminalOptions> { return this.optionsService.options; }\n  public set options(options: ITerminalOptions) {\n    for (const key in options) {\n      this.optionsService.options[key] = options[key];\n    }\n  }\n\n  constructor(\n    options: Partial<ITerminalOptions>\n  ) {\n    super();\n\n    // Setup and initialize services\n    this._instantiationService = new InstantiationService();\n    this.optionsService = this._register(new OptionsService(options));\n    this._instantiationService.setService(IOptionsService, this.optionsService);\n    this._logService = this._register(this._instantiationService.createInstance(LogService));\n    this._instantiationService.setService(ILogService, this._logService);\n    this._bufferService = this._register(this._instantiationService.createInstance(BufferService));\n    this._instantiationService.setService(IBufferService, this._bufferService);\n    this.coreService = this._register(this._instantiationService.createInstance(CoreService));\n    this._instantiationService.setService(ICoreService, this.coreService);\n    this.mouseStateService = this._register(this._instantiationService.createInstance(MouseStateService));\n    this._instantiationService.setService(IMouseStateService, this.mouseStateService);\n    this.unicodeService = this._register(this._instantiationService.createInstance(UnicodeService));\n    this._instantiationService.setService(IUnicodeService, this.unicodeService);\n    this._charsetService = this._instantiationService.createInstance(CharsetService);\n    this._instantiationService.setService(ICharsetService, this._charsetService);\n    this._oscLinkService = this._instantiationService.createInstance(OscLinkService);\n    this._instantiationService.setService(IOscLinkService, this._oscLinkService);\n\n\n    // Register input handler and handle/forward events\n    this._inputHandler = this._register(new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.mouseStateService, this.unicodeService));\n    this._register(EventUtils.forward(this._inputHandler.onLineFeed, this._onLineFeed));\n\n    // Setup listeners\n    this._register(EventUtils.forward(this._bufferService.onResize, this._onResize));\n    this._register(EventUtils.forward(this.coreService.onData, this._onData));\n    this._register(EventUtils.forward(this.coreService.onBinary, this._onBinary));\n    this._register(this.coreService.onRequestScrollToBottom(() => this.scrollToBottom(true)));\n    this._register(this.coreService.onUserInput(() =>  this._writeBuffer.handleUserInput()));\n    this._register(this.optionsService.onMultipleOptionChange(['windowsPty'], () => this._handleWindowsPtyOptionChange()));\n    this._register(this._bufferService.onScroll(() => {\n      this._onScroll.fire({ position: this._bufferService.buffer.ydisp });\n      this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);\n    }));\n    // Setup WriteBuffer\n    this._writeBuffer = this._register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)));\n    this._register(EventUtils.forward(this._writeBuffer.onWriteParsed, this._onWriteParsed));\n  }\n\n  public write(data: string | Uint8Array, callback?: () => void): void {\n    this._writeBuffer.write(data, callback);\n  }\n\n  /**\n   * Write data to terminal synchonously.\n   *\n   * This method is unreliable with async parser handlers, thus should not\n   * be used anymore. If you need blocking semantics on data input consider\n   * `write` with a callback instead.\n   *\n   * @deprecated Unreliable, will be removed soon.\n   */\n  public writeSync(data: string | Uint8Array, maxSubsequentCalls?: number): void {\n    if (this._logService.logLevel <= LogLevelEnum.WARN && !hasWriteSyncWarnHappened) {\n      this._logService.warn('writeSync is unreliable and will be removed soon.');\n      hasWriteSyncWarnHappened = true;\n    }\n    this._writeBuffer.writeSync(data, maxSubsequentCalls);\n  }\n\n  public input(data: string, wasUserInput: boolean = true): void {\n    this.coreService.triggerDataEvent(data, wasUserInput);\n  }\n\n  public resize(x: number, y: number): void {\n    if (isNaN(x) || isNaN(y)) {\n      return;\n    }\n\n    x = Math.max(x, MINIMUM_COLS);\n    y = Math.max(y, MINIMUM_ROWS);\n\n    // Flush pending writes before resize to avoid race conditions where async\n    // writes are processed with incorrect dimensions\n    this._writeBuffer.flushSync();\n\n    this._bufferService.resize(x, y);\n  }\n\n  /**\n   * Scroll the terminal down 1 row, creating a blank line.\n   * @param eraseAttr The attribute data to use the for blank line.\n   * @param isWrapped Whether the new line is wrapped from the previous line.\n   */\n  public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {\n    this._bufferService.scroll(eraseAttr, isWrapped);\n  }\n\n  /**\n   * Scroll the display of the terminal\n   * @param disp The number of lines to scroll down (negative scroll up).\n   * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used to avoid\n   * unwanted events being handled by the viewport when the event was triggered from the viewport\n   * originally.\n   */\n  public scrollLines(disp: number, suppressScrollEvent?: boolean): void {\n    this._bufferService.scrollLines(disp, suppressScrollEvent);\n  }\n\n  public scrollPages(pageCount: number): void {\n    this.scrollLines(pageCount * (this.rows - 1));\n  }\n\n  public scrollToTop(): void {\n    this.scrollLines(-this._bufferService.buffer.ydisp);\n  }\n\n  public scrollToBottom(disableSmoothScroll?: boolean): void {\n    this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);\n  }\n\n  public scrollToLine(line: number): void {\n    const scrollAmount = line - this._bufferService.buffer.ydisp;\n    if (scrollAmount !== 0) {\n      this.scrollLines(scrollAmount);\n    }\n  }\n\n  /** Add handler for ESC escape sequence. See xterm.d.ts for details. */\n  public registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable {\n    return this._inputHandler.registerEscHandler(id, callback);\n  }\n\n  /** Add handler for DCS escape sequence. See xterm.d.ts for details. */\n  public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable {\n    return this._inputHandler.registerDcsHandler(id, callback);\n  }\n\n  /** Add handler for CSI escape sequence. See xterm.d.ts for details. */\n  public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable {\n    return this._inputHandler.registerCsiHandler(id, callback);\n  }\n\n  /** Add handler for OSC escape sequence. See xterm.d.ts for details. */\n  public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this._inputHandler.registerOscHandler(ident, callback);\n  }\n\n  /** Add handler for APC escape sequence. See xterm.d.ts for details. */\n  public registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this._inputHandler.registerApcHandler(ident, callback);\n  }\n\n  protected _setup(): void {\n    this._handleWindowsPtyOptionChange();\n  }\n\n  public reset(): void {\n    this._inputHandler.reset();\n    this._bufferService.reset();\n    this._charsetService.reset();\n    this.coreService.reset();\n    this.mouseStateService.reset();\n  }\n\n\n  private _handleWindowsPtyOptionChange(): void {\n    let value = false;\n    const windowsPty = this.optionsService.rawOptions.windowsPty;\n    if (windowsPty && windowsPty.buildNumber !== undefined && windowsPty.buildNumber !== undefined) {\n      value = !!(windowsPty.backend === 'conpty' && windowsPty.buildNumber < 21376);\n    }\n    if (value) {\n      this._enableWindowsWrappingHeuristics();\n    } else {\n      this._windowsWrappingHeuristics.clear();\n    }\n  }\n\n  protected _enableWindowsWrappingHeuristics(): void {\n    if (!this._windowsWrappingHeuristics.value) {\n      const disposables: IDisposable[] = [];\n      disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService)));\n      disposables.push(this.registerCsiHandler({ final: 'H' }, () => {\n        updateWindowsModeWrappedState(this._bufferService);\n        return false;\n      }));\n      this._windowsWrappingHeuristics.value = toDisposable(() => {\n        for (const d of disposables) {\n          d.dispose();\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/Event.test.ts",
    "content": "/**\n * Copyright (c) 2024-2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { Emitter } from 'common/Event';\n\ndescribe('Emitter', () => {\n  it('should fire with 0 listeners without error', () => {\n    const emitter = new Emitter<number>();\n    emitter.fire(42);\n  });\n\n  it('should fire with 1 listener', () => {\n    const emitter = new Emitter<number>();\n    let received: number | undefined;\n    emitter.event(e => { received = e; });\n    emitter.fire(42);\n    assert.strictEqual(received, 42);\n  });\n\n  it('should fire with 1 listener using thisArgs', () => {\n    const emitter = new Emitter<number>();\n    const obj = { value: 0, handler(e: number) { this.value = e; } };\n    emitter.event(obj.handler, obj);\n    emitter.fire(42);\n    assert.strictEqual(obj.value, 42);\n  });\n\n  it('should fire with multiple listeners', () => {\n    const emitter = new Emitter<number>();\n    const results: number[] = [];\n    emitter.event(e => results.push(e * 1));\n    emitter.event(e => results.push(e * 2));\n    emitter.event(e => results.push(e * 3));\n    emitter.fire(10);\n    assert.deepEqual(results, [10, 20, 30]);\n  });\n\n  it('should handle listener removal during fire', () => {\n    const emitter = new Emitter<number>();\n    const results: string[] = [];\n    emitter.event(() => results.push('first'));\n    const disposable = emitter.event(() => {\n      results.push('second');\n      disposable.dispose();\n    });\n    emitter.event(() => results.push('third'));\n    emitter.fire(1);\n    assert.deepEqual(results, ['first', 'second', 'third']);\n  });\n\n  it('should not fire after dispose', () => {\n    const emitter = new Emitter<number>();\n    let called = false;\n    emitter.event(() => { called = true; });\n    emitter.dispose();\n    emitter.fire(42);\n    assert.strictEqual(called, false);\n  });\n\n  it('should allow disposing a listener', () => {\n    const emitter = new Emitter<number>();\n    let count = 0;\n    const disposable = emitter.event(() => { count++; });\n    emitter.fire(1);\n    disposable.dispose();\n    emitter.fire(2);\n    assert.strictEqual(count, 1);\n  });\n});\n"
  },
  {
    "path": "src/common/Event.ts",
    "content": "/**\n * Copyright (c) 2024-2026 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Minimal event utilities for xterm.js core.\n * Simplified from VS Code's event.ts - no leak detection/profiling.\n */\n\nimport { IDisposable, DisposableStore, toDisposable } from 'common/Lifecycle';\n\nexport interface IEvent<T> {\n  (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable;\n}\n\nexport class Emitter<T> {\n  private _listeners: { fn: (e: T) => any, thisArgs: any }[] = [];\n  private _disposed = false;\n  private _event: IEvent<T> | undefined;\n\n  public get event(): IEvent<T> {\n    if (this._event) {\n      return this._event;\n    }\n    this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {\n      if (this._disposed) {\n        return toDisposable(() => {});\n      }\n\n      const entry = { fn: listener, thisArgs };\n      this._listeners.push(entry);\n\n      const result = toDisposable(() => {\n        const idx = this._listeners.indexOf(entry);\n        if (idx !== -1) {\n          this._listeners.splice(idx, 1);\n        }\n      });\n\n      if (disposables) {\n        if (Array.isArray(disposables)) {\n          disposables.push(result);\n        } else {\n          disposables.add(result);\n        }\n      }\n\n      return result;\n    };\n    return this._event;\n  }\n\n  public fire(event: T): void {\n    if (this._disposed) {\n      return;\n    }\n    switch (this._listeners.length) {\n      case 0: return;\n      case 1: {\n        const { fn, thisArgs } = this._listeners[0];\n        fn.call(thisArgs, event);\n        return;\n      }\n      default: {\n        // Snapshot listeners to allow modifications during iteration (2+ listeners)\n        const listeners = this._listeners.slice();\n        for (const { fn, thisArgs } of listeners) {\n          fn.call(thisArgs, event);\n        }\n      }\n    }\n  }\n\n  public dispose(): void {\n    if (this._disposed) {\n      return;\n    }\n    this._disposed = true;\n    this._listeners.length = 0;\n  }\n}\n\nexport namespace EventUtils {\n  export function forward<T>(from: IEvent<T>, to: Emitter<T>): IDisposable {\n    return from(e => to.fire(e));\n  }\n\n  export function map<I, O>(event: IEvent<I>, map: (i: I) => O): IEvent<O> {\n    return (listener: (e: O) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {\n      return event(i => listener.call(thisArgs, map(i)), undefined, disposables);\n    };\n  }\n\n  export function any<T>(...events: IEvent<T>[]): IEvent<T>;\n  export function any(...events: IEvent<any>[]): IEvent<void>;\n  export function any<T>(...events: IEvent<T>[]): IEvent<T> {\n    return (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {\n      const store = new DisposableStore();\n      for (const event of events) {\n        store.add(event(e => listener.call(thisArgs, e)));\n      }\n      if (disposables) {\n        if (Array.isArray(disposables)) {\n          disposables.push(store);\n        } else {\n          disposables.add(store);\n        }\n      }\n      return store;\n    };\n  }\n\n  export function runAndSubscribe<T>(event: IEvent<T>, handler: (e: T) => void, initial: T): IDisposable;\n  export function runAndSubscribe<T>(event: IEvent<T>, handler: (e: T | undefined) => void): IDisposable;\n  export function runAndSubscribe<T>(event: IEvent<T>, handler: (e: T | undefined) => void, initial?: T): IDisposable {\n    handler(initial);\n    return event(e => handler(e));\n  }\n}\n"
  },
  {
    "path": "src/common/InputHandler.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { InputHandler } from 'common/InputHandler';\nimport { IBufferLine, IAttributeData, IColorEvent, ColorRequestType, SpecialColorIndex } from 'common/Types';\nimport { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { CellData } from 'common/buffer/CellData';\nimport { Attributes, BgFlags, UnderlineStyle } from 'common/buffer/Constants';\nimport { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';\nimport { Params } from 'common/parser/Params';\nimport { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockMouseStateService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test';\nimport { IBufferService, ICoreService, type IOscLinkService } from 'common/services/Services';\nimport { DEFAULT_OPTIONS } from 'common/services/OptionsService';\nimport { clone } from 'common/Clone';\nimport { BufferService } from 'common/services/BufferService';\nimport { CoreService } from 'common/services/CoreService';\nimport { OscLinkService } from 'common/services/OscLinkService';\n\n\nfunction getCursor(bufferService: IBufferService): number[] {\n  return [\n    bufferService.buffer.x,\n    bufferService.buffer.y\n  ];\n}\n\nfunction getLines(bufferService: IBufferService, limit: number = bufferService.rows): string[] {\n  const res: string[] = [];\n  for (let i = 0; i < limit; ++i) {\n    const line = bufferService.buffer.lines.get(i);\n    if (line) {\n      res.push(line.translateToString(true));\n    }\n  }\n  return res;\n}\n\nclass TestInputHandler extends InputHandler {\n  public get curAttrData(): IAttributeData { return (this as any)._curAttrData; }\n  public get windowTitleStack(): string[] { return this._windowTitleStack; }\n  public get iconNameStack(): string[] { return this._iconNameStack; }\n\n  /**\n   * Promise based parse call to await the full resolve of given input data.\n   * This is useful to test async handlers in inputhandler directly.\n   */\n  public async parseP(data: string | Uint8Array): Promise<void> {\n    let result: Promise<boolean> | void;\n    let prev: boolean | undefined;\n    while (result = this.parse(data, prev)) {\n      prev = await result;\n    }\n  }\n}\n\ndescribe('InputHandler', () => {\n  let bufferService: IBufferService;\n  let coreService: ICoreService;\n  let optionsService: MockOptionsService;\n  let oscLinkService: IOscLinkService;\n  let inputHandler: TestInputHandler;\n\n  beforeEach(() => {\n    optionsService = new MockOptionsService();\n    bufferService = new BufferService(optionsService, new MockLogService());\n    bufferService.resize(80, 30);\n    coreService = new CoreService(bufferService, new MockLogService(), optionsService);\n    oscLinkService = new OscLinkService(bufferService);\n\n    inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, oscLinkService, new MockMouseStateService(), new MockUnicodeService());\n  });\n\n  describe('SL/SR/DECIC/DECDC', () => {\n    beforeEach(() => {\n      bufferService.resize(5, 5);\n      optionsService.options.scrollback = 1;\n      bufferService.reset();\n    });\n    it('SL (scrollLeft)', async () => {\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[ @');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']);\n      await inputHandler.parseP('\\x1b[0 @');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '345', '345', '345', '345', '345']);\n      await inputHandler.parseP('\\x1b[2 @');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']);\n    });\n    it('SR (scrollRight)', async () => {\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[ A');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);\n      await inputHandler.parseP('\\x1b[0 A');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '  123', '  123', '  123', '  123', '  123']);\n      await inputHandler.parseP('\\x1b[2 A');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '    1', '    1', '    1', '    1', '    1']);\n    });\n    it('insertColumns (DECIC)', async () => {\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[3;3H');\n      await inputHandler.parseP('\\x1b[\\'}');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);\n      bufferService.reset();\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[3;3H');\n      await inputHandler.parseP('\\x1b[1\\'}');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);\n      bufferService.reset();\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[3;3H');\n      await inputHandler.parseP('\\x1b[2\\'}');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '12  3', '12  3', '12  3', '12  3', '12  3']);\n    });\n    it('deleteColumns (DECDC)', async () => {\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[3;3H');\n      await inputHandler.parseP('\\x1b[\\'~');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);\n      bufferService.reset();\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[3;3H');\n      await inputHandler.parseP('\\x1b[1\\'~');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);\n      bufferService.reset();\n      await inputHandler.parseP('12345'.repeat(6));\n      await inputHandler.parseP('\\x1b[3;3H');\n      await inputHandler.parseP('\\x1b[2\\'~');\n      assert.deepEqual(getLines(bufferService, 6), ['12345', '125', '125', '125', '125', '125']);\n    });\n  });\n\n  describe('BS with reverseWraparound set/unset', () => {\n    const ttyBS = '\\x08 \\x08';  // tty ICANON sends <BS SP BS> on pressing BS\n    beforeEach(() => {\n      bufferService.resize(5, 5);\n      optionsService.options.scrollback = 1;\n      bufferService.reset();\n    });\n    describe('reverseWraparound set', () => {\n      it('should not reverse outside of scroll margins', async () => {\n        // prepare buffer content\n        await inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');\n        assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);\n        assert.equal(bufferService.buffers.active.ydisp, 1);\n        assert.equal(bufferService.buffers.active.x, 5);\n        assert.equal(bufferService.buffers.active.y, 4);\n        await inputHandler.parseP(ttyBS.repeat(100));\n        assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', '    y']);\n\n        await inputHandler.parseP('\\x1b[?45h');\n        await inputHandler.parseP('uvwxy');\n\n        // set top/bottom to 1/3 (0-based)\n        await inputHandler.parseP('\\x1b[2;4r');\n        // place cursor below scroll bottom\n        bufferService.buffers.active.x = 5;\n        bufferService.buffers.active.y = 4;\n        await inputHandler.parseP(ttyBS.repeat(100));\n        assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', '     ']);\n\n        await inputHandler.parseP('uvwxy');\n        // place cursor within scroll margins\n        bufferService.buffers.active.x = 5;\n        bufferService.buffers.active.y = 3;\n        await inputHandler.parseP(ttyBS.repeat(100));\n        assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', '     ', '     ', '     ', 'uvwxy']);\n        assert.equal(bufferService.buffers.active.x, 0);\n        assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop);  // stops at 0, scrollTop\n\n        await inputHandler.parseP('fghijklmnopqrst');\n        // place cursor above scroll top\n        bufferService.buffers.active.x = 5;\n        bufferService.buffers.active.y = 0;\n        await inputHandler.parseP(ttyBS.repeat(100));\n        assert.deepEqual(getLines(bufferService, 6), ['#####', '     ', 'fghij', 'klmno', 'pqrst', 'uvwxy']);\n      });\n    });\n  });\n\n  it('save and restore cursor', () => {\n    bufferService.buffer.x = 1;\n    bufferService.buffer.y = 2;\n    bufferService.buffer.ybase = 0;\n    inputHandler.curAttrData.fg = 3;\n    // Save cursor position\n    inputHandler.saveCursor();\n    assert.equal(bufferService.buffer.x, 1);\n    assert.equal(bufferService.buffer.y, 2);\n    assert.equal(inputHandler.curAttrData.fg, 3);\n    // Change cursor position\n    bufferService.buffer.x = 10;\n    bufferService.buffer.y = 20;\n    inputHandler.curAttrData.fg = 30;\n    // Restore cursor position\n    inputHandler.restoreCursor();\n    assert.equal(bufferService.buffer.x, 1);\n    assert.equal(bufferService.buffer.y, 2);\n    assert.equal(inputHandler.curAttrData.fg, 3);\n  });\n  describe('DECSC/DECRC - save and restore cursor', () => {\n    it('should save and restore origin mode', async () => {\n      assert.equal(coreService.decPrivateModes.origin, false);\n      await inputHandler.parseP('\\x1b[?6h');\n      assert.equal(coreService.decPrivateModes.origin, true);\n      await inputHandler.parseP('\\x1b7');\n      await inputHandler.parseP('\\x1b[?6l');\n      assert.equal(coreService.decPrivateModes.origin, false);\n      await inputHandler.parseP('\\x1b8');\n      assert.equal(coreService.decPrivateModes.origin, true);\n    });\n    it('should save and restore wraparound mode', async () => {\n      assert.equal(coreService.decPrivateModes.wraparound, true);\n      await inputHandler.parseP('\\x1b[?7l');\n      assert.equal(coreService.decPrivateModes.wraparound, false);\n      await inputHandler.parseP('\\x1b7');\n      await inputHandler.parseP('\\x1b[?7h');\n      assert.equal(coreService.decPrivateModes.wraparound, true);\n      await inputHandler.parseP('\\x1b8');\n      assert.equal(coreService.decPrivateModes.wraparound, false);\n    });\n  });\n  describe('setCursorStyle', () => {\n    it('should call Terminal.setOption with correct params', () => {\n      inputHandler.setCursorStyle(Params.fromArray([0]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, undefined);\n      assert.equal(coreService.decPrivateModes.cursorBlink, undefined);\n\n      optionsService.options = clone(DEFAULT_OPTIONS);\n      inputHandler.setCursorStyle(Params.fromArray([1]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, 'block');\n      assert.equal(coreService.decPrivateModes.cursorBlink, true);\n\n      optionsService.options = clone(DEFAULT_OPTIONS);\n      inputHandler.setCursorStyle(Params.fromArray([2]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, 'block');\n      assert.equal(coreService.decPrivateModes.cursorBlink, false);\n\n      optionsService.options = clone(DEFAULT_OPTIONS);\n      inputHandler.setCursorStyle(Params.fromArray([3]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, 'underline');\n      assert.equal(coreService.decPrivateModes.cursorBlink, true);\n\n      optionsService.options = clone(DEFAULT_OPTIONS);\n      inputHandler.setCursorStyle(Params.fromArray([4]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, 'underline');\n      assert.equal(coreService.decPrivateModes.cursorBlink, false);\n\n      optionsService.options = clone(DEFAULT_OPTIONS);\n      inputHandler.setCursorStyle(Params.fromArray([5]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, 'bar');\n      assert.equal(coreService.decPrivateModes.cursorBlink, true);\n\n      optionsService.options = clone(DEFAULT_OPTIONS);\n      inputHandler.setCursorStyle(Params.fromArray([6]));\n      assert.equal(coreService.decPrivateModes.cursorStyle, 'bar');\n      assert.equal(coreService.decPrivateModes.cursorBlink, false);\n    });\n  });\n  describe('setMode', () => {\n    it('should toggle bracketedPasteMode', () => {\n      const coreService = new MockCoreService();\n      const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n      // Set bracketed paste mode\n      inputHandler.setModePrivate(Params.fromArray([2004]));\n      assert.equal(coreService.decPrivateModes.bracketedPasteMode, true);\n      // Reset bracketed paste mode\n      inputHandler.resetModePrivate(Params.fromArray([2004]));\n      assert.equal(coreService.decPrivateModes.bracketedPasteMode, false);\n    });\n    it('should toggle colorSchemeUpdates (DECSET 2031)', () => {\n      const coreService = new MockCoreService();\n      const optionsService = new MockOptionsService();\n      const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n      // Set color scheme updates mode (default colorSchemeQuery=true)\n      inputHandler.setModePrivate(Params.fromArray([2031]));\n      assert.equal(coreService.decPrivateModes.colorSchemeUpdates, true);\n      // Reset color scheme updates mode\n      inputHandler.resetModePrivate(Params.fromArray([2031]));\n      assert.equal(coreService.decPrivateModes.colorSchemeUpdates, false);\n    });\n    it('should not toggle colorSchemeUpdates when colorSchemeQuery is disabled', () => {\n      const coreService = new MockCoreService();\n      const optionsService = new MockOptionsService();\n      optionsService.rawOptions.vtExtensions = { colorSchemeQuery: false };\n      const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n      // Attempt to set color scheme updates mode\n      inputHandler.setModePrivate(Params.fromArray([2031]));\n      assert.equal(coreService.decPrivateModes.colorSchemeUpdates, false);\n    });\n  });\n  describe('regression tests', function (): void {\n    function termContent(bufferService: IBufferService, trim: boolean): string[] {\n      const result = [];\n      for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i)!.translateToString(trim));\n      return result;\n    }\n\n    it('insertChars', async () => {\n      const bufferService = new MockBufferService(80, 30);\n      const inputHandler = new TestInputHandler(\n        bufferService,\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService(),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n\n      // insert some data in first and second line\n      await inputHandler.parseP('a'.repeat(bufferService.cols - 10));\n      await inputHandler.parseP('1234567890');\n      await inputHandler.parseP('a'.repeat(bufferService.cols - 10));\n      await inputHandler.parseP('1234567890');\n      const line1: IBufferLine = bufferService.buffer.lines.get(0)!;\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '1234567890');\n\n      // insert one char from params = [0]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.insertChars(Params.fromArray([0]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + ' 123456789');\n\n      // insert one char from params = [1]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.insertChars(Params.fromArray([1]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '  12345678');\n\n      // insert two chars from params = [2]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.insertChars(Params.fromArray([2]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '    123456');\n\n      // insert 10 chars from params = [10]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.insertChars(Params.fromArray([10]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '          ');\n      assert.equal(line1.translateToString(true), 'a'.repeat(bufferService.cols - 10));\n    });\n    it('deleteChars', async () => {\n      const bufferService = new MockBufferService(80, 30);\n      const inputHandler = new TestInputHandler(\n        bufferService,\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService(),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n\n      // insert some data in first and second line\n      await inputHandler.parseP('a'.repeat(bufferService.cols - 10));\n      await inputHandler.parseP('1234567890');\n      await inputHandler.parseP('a'.repeat(bufferService.cols - 10));\n      await inputHandler.parseP('1234567890');\n      const line1: IBufferLine = bufferService.buffer.lines.get(0)!;\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '1234567890');\n\n      // delete one char from params = [0]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.deleteChars(Params.fromArray([0]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '234567890 ');\n      assert.equal(line1.translateToString(true), 'a'.repeat(bufferService.cols - 10) + '234567890');\n\n      // insert one char from params = [1]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.deleteChars(Params.fromArray([1]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '34567890  ');\n      assert.equal(line1.translateToString(true), 'a'.repeat(bufferService.cols - 10) + '34567890');\n\n      // insert two chars from params = [2]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.deleteChars(Params.fromArray([2]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '567890    ');\n      assert.equal(line1.translateToString(true), 'a'.repeat(bufferService.cols - 10) + '567890');\n\n\n      // insert 10 chars from params = [10]\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.deleteChars(Params.fromArray([10]));\n      assert.equal(line1.translateToString(false), 'a'.repeat(bufferService.cols - 10) + '          ');\n      assert.equal(line1.translateToString(true), 'a'.repeat(bufferService.cols - 10));\n    });\n    it('eraseInLine', async () => {\n      const bufferService = new MockBufferService(80, 30);\n      const inputHandler = new TestInputHandler(\n        bufferService,\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService(),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n\n      // fill 6 lines to test 3 different states\n      await inputHandler.parseP('a'.repeat(bufferService.cols));\n      await inputHandler.parseP('a'.repeat(bufferService.cols));\n      await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n      // params[0] - right erase\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 70;\n      inputHandler.eraseInLine(Params.fromArray([0]));\n      assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), 'a'.repeat(70) + '          ');\n\n      // params[1] - left erase\n      bufferService.buffer.y = 1;\n      bufferService.buffer.x = 70;\n      inputHandler.eraseInLine(Params.fromArray([1]));\n      assert.equal(bufferService.buffer.lines.get(1)!.translateToString(false), ' '.repeat(70) + ' aaaaaaaaa');\n\n      // params[1] - left erase\n      bufferService.buffer.y = 2;\n      bufferService.buffer.x = 70;\n      inputHandler.eraseInLine(Params.fromArray([2]));\n      assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), ' '.repeat(bufferService.cols));\n\n    });\n    it('eraseInLine reflow', async () => {\n      const bufferService = new MockBufferService(80, 30);\n      const inputHandler = new TestInputHandler(\n        bufferService,\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService(),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n\n      const resetToBaseState = async (): Promise<void> => {\n        // reset and add a wrapped line\n        bufferService.buffer.y = 0;\n        bufferService.buffer.x = 0;\n        await inputHandler.parseP('a'.repeat(bufferService.cols)); // line 0\n        await inputHandler.parseP('a'.repeat(bufferService.cols + 9)); // line 1 and 2\n        for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n        // confirm precondition that line 2 is wrapped\n        assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);\n      };\n\n      // params[0] - erase from the cursor through the end of the row.\n      await resetToBaseState();\n      bufferService.buffer.y = 2;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInLine(Params.fromArray([0]));\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);\n      bufferService.buffer.y = 2;\n      bufferService.buffer.x = 0;\n      inputHandler.eraseInLine(Params.fromArray([0]));\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);\n\n      // params[1] - erase from the beginning of the line through the cursor\n      await resetToBaseState();\n      bufferService.buffer.y = 2;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInLine(Params.fromArray([1]));\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);\n\n      // params[2] - erase complete line\n      await resetToBaseState();\n      bufferService.buffer.y = 2;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInLine(Params.fromArray([2]));\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);\n    });\n    it('ED2 with scrollOnEraseInDisplay turned on', async () => {\n      const inputHandler = new TestInputHandler(\n        bufferService,\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService({ scrollOnEraseInDisplay: true }),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n      const aLine = 'a'.repeat(bufferService.cols);\n      // add 2 full lines of text.\n      await inputHandler.parseP(aLine);\n      await inputHandler.parseP(aLine);\n\n      inputHandler.eraseInDisplay(Params.fromArray([2]));\n      // those 2 lines should have been pushed to scrollback.\n      assert.equal(bufferService.rows + 2, bufferService.buffer.lines.length);\n      assert.equal(bufferService.buffer.ybase, 2);\n      assert.equal(bufferService.buffer.lines.get(0)?.translateToString(), aLine);\n      assert.equal(bufferService.buffer.lines.get(1)?.translateToString(), aLine);\n\n      // Move to last line and add more text.\n      bufferService.buffer.y = bufferService.rows - 1;\n      bufferService.buffer.x = 0;\n      await inputHandler.parseP(aLine);\n      inputHandler.eraseInDisplay(Params.fromArray([2]));\n      // Screen should have been scrolled by a full screen size.\n      assert.equal(bufferService.rows * 2 + 2, bufferService.buffer.lines.length);\n    });\n    it('eraseInDisplay', async () => {\n      const bufferService = new MockBufferService(80, 7);\n      const inputHandler = new TestInputHandler(\n        bufferService,\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService(),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n\n      // fill display with a's\n      for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n      // params [0] - right and below erase\n      bufferService.buffer.y = 5;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInDisplay(Params.fromArray([0]));\n      assert.deepEqual(termContent(bufferService, false), [\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(40) + ' '.repeat(bufferService.cols - 40),\n        ' '.repeat(bufferService.cols)\n      ]);\n      assert.deepEqual(termContent(bufferService, true), [\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(bufferService.cols),\n        'a'.repeat(40),\n        ''\n      ]);\n\n      // reset\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 0;\n      for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n      // params [1] - left and above\n      bufferService.buffer.y = 5;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInDisplay(Params.fromArray([1]));\n      assert.deepEqual(termContent(bufferService, false), [\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(41) + 'a'.repeat(bufferService.cols - 41),\n        'a'.repeat(bufferService.cols)\n      ]);\n      assert.deepEqual(termContent(bufferService, true), [\n        '',\n        '',\n        '',\n        '',\n        '',\n        ' '.repeat(41) + 'a'.repeat(bufferService.cols - 41),\n        'a'.repeat(bufferService.cols)\n      ]);\n\n      // reset\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 0;\n      for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n      // params [2] - whole screen\n      bufferService.buffer.y = 5;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInDisplay(Params.fromArray([2]));\n      assert.deepEqual(termContent(bufferService, false), [\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols),\n        ' '.repeat(bufferService.cols)\n      ]);\n      assert.deepEqual(termContent(bufferService, true), [\n        '',\n        '',\n        '',\n        '',\n        '',\n        '',\n        ''\n      ]);\n\n      // reset and add a wrapped line\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 0;\n      await inputHandler.parseP('a'.repeat(bufferService.cols)); // line 0\n      await inputHandler.parseP('a'.repeat(bufferService.cols + 9)); // line 1 and 2\n      for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n      // params[1] left and above with wrap\n      // confirm precondition that line 2 is wrapped\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);\n      bufferService.buffer.y = 2;\n      bufferService.buffer.x = 40;\n      inputHandler.eraseInDisplay(Params.fromArray([1]));\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);\n\n      // reset and add a wrapped line\n      bufferService.buffer.y = 0;\n      bufferService.buffer.x = 0;\n      await inputHandler.parseP('a'.repeat(bufferService.cols)); // line 0\n      await inputHandler.parseP('a'.repeat(bufferService.cols + 9)); // line 1 and 2\n      for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));\n\n      // params[1] left and above with wrap\n      // confirm precondition that line 2 is wrapped\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);\n      bufferService.buffer.y = 1;\n      bufferService.buffer.x = 90; // Cursor is beyond last column\n      inputHandler.eraseInDisplay(Params.fromArray([1]));\n      assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);\n    });\n  });\n  describe('print', () => {\n    it('should not cause an infinite loop (regression test)', () => {\n      const inputHandler = new TestInputHandler(\n        new MockBufferService(80, 30),\n        new MockCharsetService(),\n        new MockCoreService(),\n        new MockLogService(),\n        new MockOptionsService(),\n        new MockOscLinkService(),\n        new MockMouseStateService(),\n        new MockUnicodeService()\n      );\n      const container = new Uint32Array(10);\n      container[0] = 0x200B;\n      inputHandler.print(container, 0, 1);\n    });\n    it('should clear cells to the right on early wrap-around', async () => {\n      bufferService.resize(5, 5);\n      optionsService.options.scrollback = 1;\n      await inputHandler.parseP('12345');\n      bufferService.buffer.x = 0;\n      await inputHandler.parseP('￥￥￥');\n      assert.deepEqual(getLines(bufferService, 2), ['￥￥', '￥']);\n    });\n    it('should strip soft hyphens (U+00AD)', async () => {\n      await inputHandler.parseP('Soft\\xadhy\\xadphen');\n      assert.strictEqual(bufferService.buffer.translateBufferLineToString(0, true), 'Softhyphen');\n      assert.strictEqual(bufferService.buffer.x, 10);\n    });\n  });\n\n  describe('alt screen', () => {\n    let bufferService: IBufferService;\n    let handler: TestInputHandler;\n\n    beforeEach(() => {\n      bufferService = new MockBufferService(80, 30);\n      handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n    });\n    it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => {\n      await handler.parseP('\\x1b[?47h\\r\\n\\x1b[31mJUNK\\x1b[?47lTEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(0, true), '');\n      assert.equal(bufferService.buffer.translateBufferLineToString(1, true), '    TEST');\n      // Text color of 'TEST' should be red\n      assert.equal((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor()), 1);\n    });\n    it('should handle DECSET/DECRST 1047 (alt screen buffer)', async () => {\n      await handler.parseP('\\x1b[?1047h\\r\\n\\x1b[31mJUNK\\x1b[?1047lTEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(0, true), '');\n      assert.equal(bufferService.buffer.translateBufferLineToString(1, true), '    TEST');\n      // Text color of 'TEST' should be red\n      assert.equal((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor()), 1);\n    });\n    it('should handle DECSET/DECRST 1048 (alt screen cursor)', async () => {\n      await handler.parseP('\\x1b[?1048h\\r\\n\\x1b[31mJUNK\\x1b[?1048lTEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(1, true), 'JUNK');\n      // Text color of 'TEST' should be default\n      assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg);\n      // Text color of 'JUNK' should be red\n      assert.equal((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor()), 1);\n    });\n    it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', async () => {\n      await handler.parseP('\\x1b[?1049h\\r\\n\\x1b[31mJUNK\\x1b[?1049lTEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(1, true), '');\n      // Text color of 'TEST' should be default\n      assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg);\n    });\n    it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', async () => {\n      await handler.parseP('\\x1b[?1049h\\r\\n\\x1b[31m\\x1b[s\\x1b[?1049lTEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST');\n      // Text color of 'TEST' should be default\n      assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg);\n      await handler.parseP('\\x1b[?1049h\\x1b[uTEST');\n      assert.equal(bufferService.buffer.translateBufferLineToString(1, true), 'TEST');\n      // Text color of 'TEST' should be red\n      assert.equal((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor()), 1);\n    });\n    it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', async () => {\n      await handler.parseP('\\x1b[42m\\x1b[?1049h');\n      // Buffer should be filled with green background\n      assert.equal(bufferService.buffer.lines.get(20)!.loadCell(10, new CellData()).getBgColor(), 2);\n    });\n  });\n\n  describe('text attributes', () => {\n    it('bold', async () => {\n      await inputHandler.parseP('\\x1b[1m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), true);\n      await inputHandler.parseP('\\x1b[22m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), false);\n    });\n    it('dim', async () => {\n      await inputHandler.parseP('\\x1b[2m');\n      assert.equal(!!inputHandler.curAttrData.isDim(), true);\n      await inputHandler.parseP('\\x1b[22m');\n      assert.equal(!!inputHandler.curAttrData.isDim(), false);\n    });\n    it('SGR 221 resets bold only (kitty)', async () => {\n      await inputHandler.parseP('\\x1b[1;2m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), true);\n      assert.equal(!!inputHandler.curAttrData.isDim(), true);\n      await inputHandler.parseP('\\x1b[221m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), false);\n      assert.equal(!!inputHandler.curAttrData.isDim(), true);\n    });\n    it('SGR 222 resets faint only (kitty)', async () => {\n      await inputHandler.parseP('\\x1b[1;2m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), true);\n      assert.equal(!!inputHandler.curAttrData.isDim(), true);\n      await inputHandler.parseP('\\x1b[222m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), true);\n      assert.equal(!!inputHandler.curAttrData.isDim(), false);\n    });\n    it('italic', async () => {\n      await inputHandler.parseP('\\x1b[3m');\n      assert.equal(!!inputHandler.curAttrData.isItalic(), true);\n      await inputHandler.parseP('\\x1b[23m');\n      assert.equal(!!inputHandler.curAttrData.isItalic(), false);\n    });\n    it('underline', async () => {\n      await inputHandler.parseP('\\x1b[4m');\n      assert.equal(!!inputHandler.curAttrData.isUnderline(), true);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(!!inputHandler.curAttrData.isUnderline(), false);\n    });\n    it('blink', async () => {\n      await inputHandler.parseP('\\x1b[5m');\n      assert.equal(!!inputHandler.curAttrData.isBlink(), true);\n      await inputHandler.parseP('\\x1b[25m');\n      assert.equal(!!inputHandler.curAttrData.isBlink(), false);\n    });\n    it('inverse', async () => {\n      await inputHandler.parseP('\\x1b[7m');\n      assert.equal(!!inputHandler.curAttrData.isInverse(), true);\n      await inputHandler.parseP('\\x1b[27m');\n      assert.equal(!!inputHandler.curAttrData.isInverse(), false);\n    });\n    it('invisible', async () => {\n      await inputHandler.parseP('\\x1b[8m');\n      assert.equal(!!inputHandler.curAttrData.isInvisible(), true);\n      await inputHandler.parseP('\\x1b[28m');\n      assert.equal(!!inputHandler.curAttrData.isInvisible(), false);\n    });\n    it('strikethrough', async () => {\n      await inputHandler.parseP('\\x1b[9m');\n      assert.equal(!!inputHandler.curAttrData.isStrikethrough(), true);\n      await inputHandler.parseP('\\x1b[29m');\n      assert.equal(!!inputHandler.curAttrData.isStrikethrough(), false);\n    });\n    it('colormode palette 16', async () => {\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT\n      // lower 8 colors\n      for (let i = 0; i < 8; ++i) {\n        await inputHandler.parseP(`\\x1b[${i + 30};${i + 40}m`);\n        assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16);\n        assert.equal(inputHandler.curAttrData.getFgColor(), i);\n        assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16);\n        assert.equal(inputHandler.curAttrData.getBgColor(), i);\n      }\n      // reset to DEFAULT\n      await inputHandler.parseP(`\\x1b[39;49m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), 0);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), 0);\n    });\n    it('colormode palette 256', async () => {\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT\n      // lower 8 colors\n      for (let i = 0; i < 256; ++i) {\n        await inputHandler.parseP(`\\x1b[38;5;${i};48;5;${i}m`);\n        assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256);\n        assert.equal(inputHandler.curAttrData.getFgColor(), i);\n        assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256);\n        assert.equal(inputHandler.curAttrData.getBgColor(), i);\n      }\n      // reset to DEFAULT\n      await inputHandler.parseP(`\\x1b[39;49m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), 0);\n      assert.equal(inputHandler.curAttrData.getFgColor(), -1);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), 0);\n      assert.equal(inputHandler.curAttrData.getBgColor(), -1);\n    });\n    it('colormode RGB', async () => {\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT\n      await inputHandler.parseP(`\\x1b[38;2;1;2;3;48;2;4;5;6m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_RGB);\n      assert.equal(inputHandler.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3);\n      assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [1, 2, 3]);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_RGB);\n      assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getBgColor()), [4, 5, 6]);\n      // reset to DEFAULT\n      await inputHandler.parseP(`\\x1b[39;49m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), 0);\n      assert.equal(inputHandler.curAttrData.getFgColor(), -1);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), 0);\n      assert.equal(inputHandler.curAttrData.getBgColor(), -1);\n    });\n    it('colormode transition RGB to 256', async () => {\n      // enter RGB for FG and BG\n      await inputHandler.parseP(`\\x1b[38;2;1;2;3;48;2;4;5;6m`);\n      // enter 256 for FG and BG\n      await inputHandler.parseP(`\\x1b[38;5;255;48;5;255m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256);\n      assert.equal(inputHandler.curAttrData.getFgColor(), 255);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256);\n      assert.equal(inputHandler.curAttrData.getBgColor(), 255);\n    });\n    it('colormode transition RGB to 16', async () => {\n      // enter RGB for FG and BG\n      await inputHandler.parseP(`\\x1b[38;2;1;2;3;48;2;4;5;6m`);\n      // enter 16 for FG and BG\n      await inputHandler.parseP(`\\x1b[37;47m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16);\n      assert.equal(inputHandler.curAttrData.getFgColor(), 7);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16);\n      assert.equal(inputHandler.curAttrData.getBgColor(), 7);\n    });\n    it('colormode transition 16 to 256', async () => {\n      // enter 16 for FG and BG\n      await inputHandler.parseP(`\\x1b[37;47m`);\n      // enter 256 for FG and BG\n      await inputHandler.parseP(`\\x1b[38;5;255;48;5;255m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256);\n      assert.equal(inputHandler.curAttrData.getFgColor(), 255);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256);\n      assert.equal(inputHandler.curAttrData.getBgColor(), 255);\n    });\n    it('colormode transition 256 to 16', async () => {\n      // enter 256 for FG and BG\n      await inputHandler.parseP(`\\x1b[38;5;255;48;5;255m`);\n      // enter 16 for FG and BG\n      await inputHandler.parseP(`\\x1b[37;47m`);\n      assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16);\n      assert.equal(inputHandler.curAttrData.getFgColor(), 7);\n      assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16);\n      assert.equal(inputHandler.curAttrData.getBgColor(), 7);\n    });\n    it('should zero missing RGB values', async () => {\n      await inputHandler.parseP(`\\x1b[38;2;1;2;3m`);\n      await inputHandler.parseP(`\\x1b[38;2;5m`);\n      assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [5, 0, 0]);\n    });\n  });\n  describe('colon notation', () => {\n    let inputHandler2: TestInputHandler;\n    beforeEach(() => {\n      inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n    });\n    describe('should equal to semicolon', () => {\n      it('CSI 38:2::50:100:150 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;50;100;150m');\n        await inputHandler.parseP('\\x1b[38:2::50:100:150m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38:2::50:100: m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;50;100;m');\n        await inputHandler.parseP('\\x1b[38:2::50:100:m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38:2::50:: m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;50;;m');\n        await inputHandler.parseP('\\x1b[38:2::50::m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38:2:::: m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;;;m');\n        await inputHandler.parseP('\\x1b[38:2::::m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38;2::50:100:150 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;50;100;150m');\n        await inputHandler.parseP('\\x1b[38;2::50:100:150m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38;2;50:100:150 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;50;100;150m');\n        await inputHandler.parseP('\\x1b[38;2;50:100:150m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38;2;50;100:150 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2;50;100;150m');\n        await inputHandler.parseP('\\x1b[38;2;50;100:150m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38:5:50 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;5;50m');\n        await inputHandler.parseP('\\x1b[38:5:50m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38:5: m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;5;m');\n        await inputHandler.parseP('\\x1b[38:5:m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38;5:50 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;5;50m');\n        await inputHandler.parseP('\\x1b[38;5:50m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n    });\n    describe('should fill early sequence end with default of 0', () => {\n      it('CSI 38:2 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;2m');\n        await inputHandler.parseP('\\x1b[38:2m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 38:5 m', async () => {\n        inputHandler.curAttrData.fg = 0xFFFFFFFF;\n        inputHandler2.curAttrData.fg = 0xFFFFFFFF;\n        await inputHandler2.parseP('\\x1b[38;5m');\n        await inputHandler.parseP('\\x1b[38:5m');\n        assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n    });\n    describe('should not interfere with leading/following SGR attrs', () => {\n      it('CSI 1 ; 38:2::50:100:150 ; 4 m', async () => {\n        await inputHandler2.parseP('\\x1b[1;38;2;50;100;150;4m');\n        await inputHandler.parseP('\\x1b[1;38:2::50:100:150;4m');\n        assert.equal(!!inputHandler2.curAttrData.isBold(), true);\n        assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 1 ; 38:2::50:100: ; 4 m', async () => {\n        await inputHandler2.parseP('\\x1b[1;38;2;50;100;;4m');\n        await inputHandler.parseP('\\x1b[1;38:2::50:100:;4m');\n        assert.equal(!!inputHandler2.curAttrData.isBold(), true);\n        assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 1 ; 38:2::50:100 ; 4 m', async () => {\n        await inputHandler2.parseP('\\x1b[1;38;2;50;100;;4m');\n        await inputHandler.parseP('\\x1b[1;38:2::50:100;4m');\n        assert.equal(!!inputHandler2.curAttrData.isBold(), true);\n        assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 1 ; 38:2:: ; 4 m', async () => {\n        await inputHandler2.parseP('\\x1b[1;38;2;;;;4m');\n        await inputHandler.parseP('\\x1b[1;38:2::;4m');\n        assert.equal(!!inputHandler2.curAttrData.isBold(), true);\n        assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n      it('CSI 1 ; 38;2:: ; 4 m', async () => {\n        await inputHandler2.parseP('\\x1b[1;38;2;;;;4m');\n        await inputHandler.parseP('\\x1b[1;38;2::;4m');\n        assert.equal(!!inputHandler2.curAttrData.isBold(), true);\n        assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);\n        assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0);\n        assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);\n      });\n    });\n  });\n  describe('cursor positioning', () => {\n    beforeEach(() => {\n      bufferService.resize(10, 10);\n    });\n    it('cursor forward (CUF)', async () => {\n      await inputHandler.parseP('\\x1b[C');\n      assert.deepEqual(getCursor(bufferService), [1, 0]);\n      await inputHandler.parseP('\\x1b[1C');\n      assert.deepEqual(getCursor(bufferService), [2, 0]);\n      await inputHandler.parseP('\\x1b[4C');\n      assert.deepEqual(getCursor(bufferService), [6, 0]);\n      await inputHandler.parseP('\\x1b[100C');\n      assert.deepEqual(getCursor(bufferService), [9, 0]);\n      // should not change y\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 4;\n      await inputHandler.parseP('\\x1b[C');\n      assert.deepEqual(getCursor(bufferService), [9, 4]);\n    });\n    it('cursor backward (CUB)', async () => {\n      await inputHandler.parseP('\\x1b[D');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[1D');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      // place cursor at end of first line\n      await inputHandler.parseP('\\x1b[100C');\n      await inputHandler.parseP('\\x1b[D');\n      assert.deepEqual(getCursor(bufferService), [8, 0]);\n      await inputHandler.parseP('\\x1b[1D');\n      assert.deepEqual(getCursor(bufferService), [7, 0]);\n      await inputHandler.parseP('\\x1b[4D');\n      assert.deepEqual(getCursor(bufferService), [3, 0]);\n      await inputHandler.parseP('\\x1b[100D');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      // should not change y\n      bufferService.buffer.x = 4;\n      bufferService.buffer.y = 4;\n      await inputHandler.parseP('\\x1b[D');\n      assert.deepEqual(getCursor(bufferService), [3, 4]);\n    });\n    it('cursor down (CUD)', async () => {\n      await inputHandler.parseP('\\x1b[B');\n      assert.deepEqual(getCursor(bufferService), [0, 1]);\n      await inputHandler.parseP('\\x1b[1B');\n      assert.deepEqual(getCursor(bufferService), [0, 2]);\n      await inputHandler.parseP('\\x1b[4B');\n      assert.deepEqual(getCursor(bufferService), [0, 6]);\n      await inputHandler.parseP('\\x1b[100B');\n      assert.deepEqual(getCursor(bufferService), [0, 9]);\n      // should not change x\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 0;\n      await inputHandler.parseP('\\x1b[B');\n      assert.deepEqual(getCursor(bufferService), [8, 1]);\n    });\n    it('cursor up (CUU)', async () => {\n      await inputHandler.parseP('\\x1b[A');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[1A');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      // place cursor at beginning of last row\n      await inputHandler.parseP('\\x1b[100B');\n      await inputHandler.parseP('\\x1b[A');\n      assert.deepEqual(getCursor(bufferService), [0, 8]);\n      await inputHandler.parseP('\\x1b[1A');\n      assert.deepEqual(getCursor(bufferService), [0, 7]);\n      await inputHandler.parseP('\\x1b[4A');\n      assert.deepEqual(getCursor(bufferService), [0, 3]);\n      await inputHandler.parseP('\\x1b[100A');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      // should not change x\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 9;\n      await inputHandler.parseP('\\x1b[A');\n      assert.deepEqual(getCursor(bufferService), [8, 8]);\n    });\n    it('cursor next line (CNL)', async () => {\n      await inputHandler.parseP('\\x1b[E');\n      assert.deepEqual(getCursor(bufferService), [0, 1]);\n      await inputHandler.parseP('\\x1b[1E');\n      assert.deepEqual(getCursor(bufferService), [0, 2]);\n      await inputHandler.parseP('\\x1b[4E');\n      assert.deepEqual(getCursor(bufferService), [0, 6]);\n      await inputHandler.parseP('\\x1b[100E');\n      assert.deepEqual(getCursor(bufferService), [0, 9]);\n      // should reset x to zero\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 0;\n      await inputHandler.parseP('\\x1b[E');\n      assert.deepEqual(getCursor(bufferService), [0, 1]);\n    });\n    it('cursor previous line (CPL)', async () => {\n      await inputHandler.parseP('\\x1b[F');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[1F');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      // place cursor at beginning of last row\n      await inputHandler.parseP('\\x1b[100E');\n      await inputHandler.parseP('\\x1b[F');\n      assert.deepEqual(getCursor(bufferService), [0, 8]);\n      await inputHandler.parseP('\\x1b[1F');\n      assert.deepEqual(getCursor(bufferService), [0, 7]);\n      await inputHandler.parseP('\\x1b[4F');\n      assert.deepEqual(getCursor(bufferService), [0, 3]);\n      await inputHandler.parseP('\\x1b[100F');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      // should reset x to zero\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 9;\n      await inputHandler.parseP('\\x1b[F');\n      assert.deepEqual(getCursor(bufferService), [0, 8]);\n    });\n    it('cursor character absolute (CHA)', async () => {\n      await inputHandler.parseP('\\x1b[G');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[1G');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[2G');\n      assert.deepEqual(getCursor(bufferService), [1, 0]);\n      await inputHandler.parseP('\\x1b[5G');\n      assert.deepEqual(getCursor(bufferService), [4, 0]);\n      await inputHandler.parseP('\\x1b[100G');\n      assert.deepEqual(getCursor(bufferService), [9, 0]);\n    });\n    it('cursor position (CUP)', async () => {\n      bufferService.buffer.x = 5;\n      bufferService.buffer.y = 5;\n      await inputHandler.parseP('\\x1b[H');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      bufferService.buffer.x = 5;\n      bufferService.buffer.y = 5;\n      await inputHandler.parseP('\\x1b[1H');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      bufferService.buffer.x = 5;\n      bufferService.buffer.y = 5;\n      await inputHandler.parseP('\\x1b[1;1H');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      bufferService.buffer.x = 5;\n      bufferService.buffer.y = 5;\n      await inputHandler.parseP('\\x1b[8H');\n      assert.deepEqual(getCursor(bufferService), [0, 7]);\n      bufferService.buffer.x = 5;\n      bufferService.buffer.y = 5;\n      await inputHandler.parseP('\\x1b[;8H');\n      assert.deepEqual(getCursor(bufferService), [7, 0]);\n      bufferService.buffer.x = 5;\n      bufferService.buffer.y = 5;\n      await inputHandler.parseP('\\x1b[100;100H');\n      assert.deepEqual(getCursor(bufferService), [9, 9]);\n    });\n    it('horizontal position absolute (HPA)', async () => {\n      await inputHandler.parseP('\\x1b[`');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[1`');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[2`');\n      assert.deepEqual(getCursor(bufferService), [1, 0]);\n      await inputHandler.parseP('\\x1b[5`');\n      assert.deepEqual(getCursor(bufferService), [4, 0]);\n      await inputHandler.parseP('\\x1b[100`');\n      assert.deepEqual(getCursor(bufferService), [9, 0]);\n    });\n    it('horizontal position relative (HPR)', async () => {\n      await inputHandler.parseP('\\x1b[a');\n      assert.deepEqual(getCursor(bufferService), [1, 0]);\n      await inputHandler.parseP('\\x1b[1a');\n      assert.deepEqual(getCursor(bufferService), [2, 0]);\n      await inputHandler.parseP('\\x1b[4a');\n      assert.deepEqual(getCursor(bufferService), [6, 0]);\n      await inputHandler.parseP('\\x1b[100a');\n      assert.deepEqual(getCursor(bufferService), [9, 0]);\n      // should not change y\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 4;\n      await inputHandler.parseP('\\x1b[a');\n      assert.deepEqual(getCursor(bufferService), [9, 4]);\n    });\n    it('vertical position absolute (VPA)', async () => {\n      await inputHandler.parseP('\\x1b[d');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[1d');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n      await inputHandler.parseP('\\x1b[2d');\n      assert.deepEqual(getCursor(bufferService), [0, 1]);\n      await inputHandler.parseP('\\x1b[5d');\n      assert.deepEqual(getCursor(bufferService), [0, 4]);\n      await inputHandler.parseP('\\x1b[100d');\n      assert.deepEqual(getCursor(bufferService), [0, 9]);\n      // should not change x\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 4;\n      await inputHandler.parseP('\\x1b[d');\n      assert.deepEqual(getCursor(bufferService), [8, 0]);\n    });\n    it('vertical position relative (VPR)', async () => {\n      await inputHandler.parseP('\\x1b[e');\n      assert.deepEqual(getCursor(bufferService), [0, 1]);\n      await inputHandler.parseP('\\x1b[1e');\n      assert.deepEqual(getCursor(bufferService), [0, 2]);\n      await inputHandler.parseP('\\x1b[4e');\n      assert.deepEqual(getCursor(bufferService), [0, 6]);\n      await inputHandler.parseP('\\x1b[100e');\n      assert.deepEqual(getCursor(bufferService), [0, 9]);\n      // should not change x\n      bufferService.buffer.x = 8;\n      bufferService.buffer.y = 4;\n      await inputHandler.parseP('\\x1b[e');\n      assert.deepEqual(getCursor(bufferService), [8, 5]);\n    });\n    describe('should clamp cursor into addressible range', () => {\n      it('CUF', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[C');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[C');\n        assert.deepEqual(getCursor(bufferService), [1, 0]);\n      });\n      it('CUB', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[D');\n        assert.deepEqual(getCursor(bufferService), [8, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[D');\n        assert.deepEqual(getCursor(bufferService), [0, 0]);\n      });\n      it('CUD', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[B');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[B');\n        assert.deepEqual(getCursor(bufferService), [0, 1]);\n      });\n      it('CUU', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[A');\n        assert.deepEqual(getCursor(bufferService), [9, 8]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[A');\n        assert.deepEqual(getCursor(bufferService), [0, 0]);\n      });\n      it('CNL', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[E');\n        assert.deepEqual(getCursor(bufferService), [0, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[E');\n        assert.deepEqual(getCursor(bufferService), [0, 1]);\n      });\n      it('CPL', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[F');\n        assert.deepEqual(getCursor(bufferService), [0, 8]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[F');\n        assert.deepEqual(getCursor(bufferService), [0, 0]);\n      });\n      it('CHA', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[5G');\n        assert.deepEqual(getCursor(bufferService), [4, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[5G');\n        assert.deepEqual(getCursor(bufferService), [4, 0]);\n      });\n      it('CUP', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[5;5H');\n        assert.deepEqual(getCursor(bufferService), [4, 4]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[5;5H');\n        assert.deepEqual(getCursor(bufferService), [4, 4]);\n      });\n      it('HPA', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[5`');\n        assert.deepEqual(getCursor(bufferService), [4, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[5`');\n        assert.deepEqual(getCursor(bufferService), [4, 0]);\n      });\n      it('HPR', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[a');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[a');\n        assert.deepEqual(getCursor(bufferService), [1, 0]);\n      });\n      it('VPA', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[5d');\n        assert.deepEqual(getCursor(bufferService), [9, 4]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[5d');\n        assert.deepEqual(getCursor(bufferService), [0, 4]);\n      });\n      it('VPR', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[e');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[e');\n        assert.deepEqual(getCursor(bufferService), [0, 1]);\n      });\n      it('DCH', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[P');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[P');\n        assert.deepEqual(getCursor(bufferService), [0, 0]);\n      });\n      it('DCH - should delete last cell', async () => {\n        await inputHandler.parseP('0123456789\\x1b[P');\n        assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 ');\n      });\n      it('ECH', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[X');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[X');\n        assert.deepEqual(getCursor(bufferService), [0, 0]);\n      });\n      it('ECH - should delete last cell', async () => {\n        await inputHandler.parseP('0123456789\\x1b[X');\n        assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 ');\n      });\n      it('ICH', async () => {\n        bufferService.buffer.x = 10000;\n        bufferService.buffer.y = 10000;\n        await inputHandler.parseP('\\x1b[@');\n        assert.deepEqual(getCursor(bufferService), [9, 9]);\n        bufferService.buffer.x = -10000;\n        bufferService.buffer.y = -10000;\n        await inputHandler.parseP('\\x1b[@');\n        assert.deepEqual(getCursor(bufferService), [0, 0]);\n      });\n      it('ICH - should delete last cell', async () => {\n        await inputHandler.parseP('0123456789\\x1b[@');\n        assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 ');\n      });\n    });\n  });\n  describe('DECSTBM - scroll margins', () => {\n    beforeEach(() => {\n      bufferService.resize(10, 10);\n    });\n    it('should default to whole viewport', async () => {\n      await inputHandler.parseP('\\x1b[r');\n      assert.equal(bufferService.buffer.scrollTop, 0);\n      assert.equal(bufferService.buffer.scrollBottom, 9);\n      await inputHandler.parseP('\\x1b[3;7r');\n      assert.equal(bufferService.buffer.scrollTop, 2);\n      assert.equal(bufferService.buffer.scrollBottom, 6);\n      await inputHandler.parseP('\\x1b[0;0r');\n      assert.equal(bufferService.buffer.scrollTop, 0);\n      assert.equal(bufferService.buffer.scrollBottom, 9);\n    });\n    it('should clamp bottom', async () => {\n      await inputHandler.parseP('\\x1b[3;1000r');\n      assert.equal(bufferService.buffer.scrollTop, 2);\n      assert.equal(bufferService.buffer.scrollBottom, 9);\n    });\n    it('should only apply for top < bottom', async () => {\n      await inputHandler.parseP('\\x1b[7;2r');\n      assert.equal(bufferService.buffer.scrollTop, 0);\n      assert.equal(bufferService.buffer.scrollBottom, 9);\n    });\n    it('should home cursor', async () => {\n      bufferService.buffer.x = 10000;\n      bufferService.buffer.y = 10000;\n      await inputHandler.parseP('\\x1b[2;7r');\n      assert.deepEqual(getCursor(bufferService), [0, 0]);\n    });\n  });\n  describe('scroll margins', () => {\n    beforeEach(() => {\n      bufferService.resize(10, 10);\n    });\n    it('scrollUp', async () => {\n      await inputHandler.parseP('0\\r\\n1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\\r\\n6\\r\\n7\\r\\n8\\r\\n9\\x1b[2;4r\\x1b[2Sm');\n      assert.deepEqual(getLines(bufferService), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);\n    });\n    it('scrollDown', async () => {\n      await inputHandler.parseP('0\\r\\n1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\\r\\n6\\r\\n7\\r\\n8\\r\\n9\\x1b[2;4r\\x1b[2Tm');\n      assert.deepEqual(getLines(bufferService), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']);\n    });\n    it('insertLines - out of margins', async () => {\n      await inputHandler.parseP('0\\r\\n1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\\r\\n6\\r\\n7\\r\\n8\\r\\n9\\x1b[3;6r');\n      assert.equal(bufferService.buffer.scrollTop, 2);\n      assert.equal(bufferService.buffer.scrollBottom, 5);\n      await inputHandler.parseP('\\x1b[2Lm');\n      assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']);\n      await inputHandler.parseP('\\x1b[2H\\x1b[2Ln');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']);\n      // skip below scrollbottom\n      await inputHandler.parseP('\\x1b[7H\\x1b[2Lo');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']);\n      await inputHandler.parseP('\\x1b[8H\\x1b[2Lp');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']);\n      await inputHandler.parseP('\\x1b[100H\\x1b[2Lq');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']);\n    });\n    it('insertLines - within margins', async () => {\n      await inputHandler.parseP('0\\r\\n1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\\r\\n6\\r\\n7\\r\\n8\\r\\n9\\x1b[3;6r');\n      assert.equal(bufferService.buffer.scrollTop, 2);\n      assert.equal(bufferService.buffer.scrollBottom, 5);\n      await inputHandler.parseP('\\x1b[3H\\x1b[2Lm');\n      assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']);\n      await inputHandler.parseP('\\x1b[6H\\x1b[2Ln');\n      assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']);\n    });\n    it('deleteLines - out of margins', async () => {\n      await inputHandler.parseP('0\\r\\n1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\\r\\n6\\r\\n7\\r\\n8\\r\\n9\\x1b[3;6r');\n      assert.equal(bufferService.buffer.scrollTop, 2);\n      assert.equal(bufferService.buffer.scrollBottom, 5);\n      await inputHandler.parseP('\\x1b[2Mm');\n      assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']);\n      await inputHandler.parseP('\\x1b[2H\\x1b[2Mn');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']);\n      // skip below scrollbottom\n      await inputHandler.parseP('\\x1b[7H\\x1b[2Mo');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']);\n      await inputHandler.parseP('\\x1b[8H\\x1b[2Mp');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']);\n      await inputHandler.parseP('\\x1b[100H\\x1b[2Mq');\n      assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']);\n    });\n    it('deleteLines - within margins', async () => {\n      await inputHandler.parseP('0\\r\\n1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\\r\\n6\\r\\n7\\r\\n8\\r\\n9\\x1b[3;6r');\n      assert.equal(bufferService.buffer.scrollTop, 2);\n      assert.equal(bufferService.buffer.scrollBottom, 5);\n      await inputHandler.parseP('\\x1b[6H\\x1b[2Mm');\n      assert.deepEqual(getLines(bufferService), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']);\n      await inputHandler.parseP('\\x1b[3H\\x1b[2Mn');\n      assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);\n    });\n  });\n  it('should parse big chunks in smaller subchunks', async () => {\n    // max single chunk size is hardcoded as 131072\n    const calls: any[] = [];\n    bufferService.resize(10, 10);\n    (inputHandler as any)._parser.parse = (data: Uint32Array, length: number) => {\n      calls.push([data.length, length]);\n    };\n    await inputHandler.parseP('12345');\n    await inputHandler.parseP('a'.repeat(10000));\n    await inputHandler.parseP('a'.repeat(200000));\n    await inputHandler.parseP('a'.repeat(300000));\n    assert.deepEqual(calls, [\n      [4096, 5],\n      [10000, 10000],\n      [131072, 131072], [131072, 200000 - 131072],\n      [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]\n    ]);\n  });\n  describe('windowOptions', () => {\n    it('all should be disabled by default and not report', async () => {\n      bufferService.resize(10, 10);\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[14t');\n      await inputHandler.parseP('\\x1b[16t');\n      await inputHandler.parseP('\\x1b[18t');\n      await inputHandler.parseP('\\x1b[20t');\n      await inputHandler.parseP('\\x1b[21t');\n      assert.deepEqual(stack, []);\n    });\n    it('14 - GetWinSizePixels', async () => {\n      bufferService.resize(10, 10);\n      optionsService.options.windowOptions.getWinSizePixels = true;\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[14t');\n      // does not report in test terminal due to missing renderer\n      assert.deepEqual(stack, []);\n    });\n    it('16 - GetCellSizePixels', async () => {\n      bufferService.resize(10, 10);\n      optionsService.options.windowOptions.getCellSizePixels = true;\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[16t');\n      // does not report in test terminal due to missing renderer\n      assert.deepEqual(stack, []);\n    });\n    it('18 - GetWinSizeChars', async () => {\n      bufferService.resize(10, 10);\n      optionsService.options.windowOptions.getWinSizeChars = true;\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[18t');\n      assert.deepEqual(stack, ['\\x1b[8;10;10t']);\n      bufferService.resize(50, 20);\n      await inputHandler.parseP('\\x1b[18t');\n      assert.deepEqual(stack, ['\\x1b[8;10;10t', '\\x1b[8;20;50t']);\n    });\n    it('22/23 - PushTitle/PopTitle', async () => {\n      bufferService.resize(10, 10);\n      optionsService.options.windowOptions.pushTitle = true;\n      optionsService.options.windowOptions.popTitle = true;\n      const stack: string[] = [];\n      inputHandler.onTitleChange(data => stack.push(data));\n      await inputHandler.parseP('\\x1b]0;1\\x07');\n      await inputHandler.parseP('\\x1b[22t');\n      await inputHandler.parseP('\\x1b]0;2\\x07');\n      await inputHandler.parseP('\\x1b[22t');\n      await inputHandler.parseP('\\x1b]0;3\\x07');\n      await inputHandler.parseP('\\x1b[22t');\n      assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']);\n      assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']);\n      assert.deepEqual(stack, ['1', '2', '3']);\n      await inputHandler.parseP('\\x1b[23t');\n      await inputHandler.parseP('\\x1b[23t');\n      await inputHandler.parseP('\\x1b[23t');\n      await inputHandler.parseP('\\x1b[23t'); // one more to test \"overflow\"\n      assert.deepEqual(inputHandler.windowTitleStack, []);\n      assert.deepEqual(inputHandler.iconNameStack, []);\n      assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']);\n    });\n    it('22/23 - PushTitle/PopTitle with ;1', async () => {\n      bufferService.resize(10, 10);\n      optionsService.options.windowOptions.pushTitle = true;\n      optionsService.options.windowOptions.popTitle = true;\n      const stack: string[] = [];\n      inputHandler.onTitleChange(data => stack.push(data));\n      await inputHandler.parseP('\\x1b]0;1\\x07');\n      await inputHandler.parseP('\\x1b[22;1t');\n      await inputHandler.parseP('\\x1b]0;2\\x07');\n      await inputHandler.parseP('\\x1b[22;1t');\n      await inputHandler.parseP('\\x1b]0;3\\x07');\n      await inputHandler.parseP('\\x1b[22;1t');\n      assert.deepEqual(inputHandler.windowTitleStack, []);\n      assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']);\n      assert.deepEqual(stack, ['1', '2', '3']);\n      await inputHandler.parseP('\\x1b[23;1t');\n      await inputHandler.parseP('\\x1b[23;1t');\n      await inputHandler.parseP('\\x1b[23;1t');\n      await inputHandler.parseP('\\x1b[23;1t'); // one more to test \"overflow\"\n      assert.deepEqual(inputHandler.windowTitleStack, []);\n      assert.deepEqual(inputHandler.iconNameStack, []);\n      assert.deepEqual(stack, ['1', '2', '3']);\n    });\n    it('22/23 - PushTitle/PopTitle with ;2', async () => {\n      bufferService.resize(10, 10);\n      optionsService.options.windowOptions.pushTitle = true;\n      optionsService.options.windowOptions.popTitle = true;\n      const stack: string[] = [];\n      inputHandler.onTitleChange(data => stack.push(data));\n      await inputHandler.parseP('\\x1b]0;1\\x07');\n      await inputHandler.parseP('\\x1b[22;2t');\n      await inputHandler.parseP('\\x1b]0;2\\x07');\n      await inputHandler.parseP('\\x1b[22;2t');\n      await inputHandler.parseP('\\x1b]0;3\\x07');\n      await inputHandler.parseP('\\x1b[22;2t');\n      assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']);\n      assert.deepEqual(inputHandler.iconNameStack, []);\n      assert.deepEqual(stack, ['1', '2', '3']);\n      await inputHandler.parseP('\\x1b[23;2t');\n      await inputHandler.parseP('\\x1b[23;2t');\n      await inputHandler.parseP('\\x1b[23;2t');\n      await inputHandler.parseP('\\x1b[23;2t'); // one more to test \"overflow\"\n      assert.deepEqual(inputHandler.windowTitleStack, []);\n      assert.deepEqual(inputHandler.iconNameStack, []);\n      assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']);\n    });\n    it('DECCOLM - should only work with \"SetWinLines\" (24) enabled', async () => {\n      // disabled\n      bufferService.resize(10, 10);\n      await inputHandler.parseP('\\x1b[?3l');\n      assert.equal(bufferService.cols, 10);\n      await inputHandler.parseP('\\x1b[?3h');\n      assert.equal(bufferService.cols, 10);\n      // enabled\n      inputHandler.reset();\n      optionsService.options.windowOptions.setWinLines = true;\n      await inputHandler.parseP('\\x1b[?3l');\n      assert.equal(bufferService.cols, 80);\n      await inputHandler.parseP('\\x1b[?3h');\n      assert.equal(bufferService.cols, 132);\n    });\n  });\n  describe('XTVERSION (CSI > q, CSI > 0 q)', () => {\n    it('should report xterm.js version', async () => {\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[>q');\n      assert.strictEqual(stack.length, 1);\n      assert.ok(stack[0].match(/^\\x1bP>\\|xterm\\.js\\(\\d+\\.\\d+\\.\\d+(-beta\\.\\d+)?\\)\\x1b\\\\/));\n    });\n    it('should report xterm.js version for CSI > 0 q', async () => {\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[>0q');\n      assert.strictEqual(stack.length, 1);\n      assert.ok(stack[0].match(/^\\x1bP>\\|xterm\\.js\\(\\d+\\.\\d+\\.\\d+(-beta\\.\\d+)?\\)\\x1b\\\\/));\n    });\n    it('should not report for CSI > 1 q', async () => {\n      const stack: string[] = [];\n      coreService.onData(data => stack.push(data));\n      await inputHandler.parseP('\\x1b[>1q');\n      assert.strictEqual(stack.length, 0);\n    });\n  });\n  describe('should correctly reset cells taken by wide chars', () => {\n    beforeEach(async () => {\n      bufferService.resize(10, 5);\n      optionsService.options.scrollback = 1;\n      await inputHandler.parseP('￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥');\n    });\n    it('print', async () => {\n      await inputHandler.parseP('\\x1b[H#');\n      assert.deepEqual(getLines(bufferService), ['# ￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[1;6H######');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '# ￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('#');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '##￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('#');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '### ￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[3;9H#');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '### ￥￥￥', '￥￥￥￥#', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('#');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '### ￥￥￥', '￥￥￥￥##', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('#');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '### ￥￥￥', '￥￥￥￥##', '# ￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[4;10H#');\n      assert.deepEqual(getLines(bufferService), ['# ￥ #####', '### ￥￥￥', '￥￥￥￥##', '# ￥￥￥ #', '']);\n    });\n    it('EL', async () => {\n      await inputHandler.parseP('\\x1b[1;6H\\x1b[K#');\n      assert.deepEqual(getLines(bufferService), ['￥￥ #', '￥￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[2;5H\\x1b[1K');\n      assert.deepEqual(getLines(bufferService), ['￥￥ #', '      ￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[3;6H\\x1b[1K');\n      assert.deepEqual(getLines(bufferService), ['￥￥ #', '      ￥￥', '      ￥￥', '￥￥￥￥￥', '']);\n    });\n    it('ICH', async () => {\n      await inputHandler.parseP('\\x1b[1;6H\\x1b[@');\n      assert.deepEqual(getLines(bufferService), ['￥￥   ￥', '￥￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[2;4H\\x1b[2@');\n      assert.deepEqual(getLines(bufferService), ['￥￥   ￥', '￥    ￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[3;4H\\x1b[3@');\n      assert.deepEqual(getLines(bufferService), ['￥￥   ￥', '￥    ￥￥', '￥     ￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[4;4H\\x1b[4@');\n      assert.deepEqual(getLines(bufferService), ['￥￥   ￥', '￥    ￥￥', '￥     ￥', '￥      ￥', '']);\n    });\n    it('DCH', async () => {\n      await inputHandler.parseP('\\x1b[1;6H\\x1b[P');\n      assert.deepEqual(getLines(bufferService), ['￥￥ ￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[2;6H\\x1b[2P');\n      assert.deepEqual(getLines(bufferService), ['￥￥ ￥￥', '￥￥  ￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[3;6H\\x1b[3P');\n      assert.deepEqual(getLines(bufferService), ['￥￥ ￥￥', '￥￥  ￥', '￥￥ ￥', '￥￥￥￥￥', '']);\n    });\n    it('ECH', async () => {\n      await inputHandler.parseP('\\x1b[1;6H\\x1b[X');\n      assert.deepEqual(getLines(bufferService), ['￥￥  ￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[2;6H\\x1b[2X');\n      assert.deepEqual(getLines(bufferService), ['￥￥  ￥￥', '￥￥    ￥', '￥￥￥￥￥', '￥￥￥￥￥', '']);\n      await inputHandler.parseP('\\x1b[3;6H\\x1b[3X');\n      assert.deepEqual(getLines(bufferService), ['￥￥  ￥￥', '￥￥    ￥', '￥￥    ￥', '￥￥￥￥￥', '']);\n    });\n  });\n\n  describe('BS with reverseWraparound set/unset', () => {\n    const ttyBS = '\\x08 \\x08';  // tty ICANON sends <BS SP BS> on pressing BS\n    beforeEach(() => {\n      bufferService.resize(5, 5);\n      optionsService.options.scrollback = 1;\n    });\n    describe('reverseWraparound unset (default)', () => {\n      it('cannot delete last cell', async () => {\n        await inputHandler.parseP('12345');\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 1), ['123 5']);\n        await inputHandler.parseP(ttyBS.repeat(10));\n        assert.deepEqual(getLines(bufferService, 1), ['    5']);\n      });\n      it('cannot access prev line', async () => {\n        await inputHandler.parseP('12345'.repeat(2));\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['12345', '123 5']);\n        await inputHandler.parseP(ttyBS.repeat(10));\n        assert.deepEqual(getLines(bufferService, 2), ['12345', '    5']);\n      });\n    });\n    describe('reverseWraparound set', () => {\n      it('can delete last cell', async () => {\n        await inputHandler.parseP('\\x1b[?45h');\n        await inputHandler.parseP('12345');\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 1), ['1234 ']);\n        await inputHandler.parseP(ttyBS.repeat(7));\n        assert.deepEqual(getLines(bufferService, 1), ['     ']);\n      });\n      it('can access prev line if wrapped', async () => {\n        await inputHandler.parseP('\\x1b[?45h');\n        await inputHandler.parseP('12345'.repeat(2));\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['12345', '1234 ']);\n        await inputHandler.parseP(ttyBS.repeat(7));\n        assert.deepEqual(getLines(bufferService, 2), ['12   ', '     ']);\n      });\n      it('should lift isWrapped', async () => {\n        await inputHandler.parseP('\\x1b[?45h');\n        await inputHandler.parseP('12345'.repeat(2));\n        assert.equal(bufferService.buffer.lines.get(1)?.isWrapped, true);\n        await inputHandler.parseP(ttyBS.repeat(7));\n        assert.equal(bufferService.buffer.lines.get(1)?.isWrapped, false);\n      });\n      it('stops at hard NLs', async () => {\n        await inputHandler.parseP('\\x1b[?45h');\n        await inputHandler.parseP('12345\\r\\n');\n        await inputHandler.parseP('12345'.repeat(2));\n        await inputHandler.parseP(ttyBS.repeat(50));\n        assert.deepEqual(getLines(bufferService, 3), ['12345', '     ', '     ']);\n        assert.equal(bufferService.buffer.x, 0);\n        assert.equal(bufferService.buffer.y, 1);\n      });\n      it('handles wide chars correctly', async () => {\n        await inputHandler.parseP('\\x1b[?45h');\n        await inputHandler.parseP('￥￥￥');\n        assert.deepEqual(getLines(bufferService, 2), ['￥￥', '￥']);\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['￥￥', '  ']);\n        assert.equal(bufferService.buffer.x, 1);\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['￥￥', '  ']);\n        assert.equal(bufferService.buffer.x, 0);\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['￥  ', '  ']);\n        assert.equal(bufferService.buffer.x, 3);  // x=4 skipped due to early wrap-around\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['￥  ', '  ']);\n        assert.equal(bufferService.buffer.x, 2);\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['    ', '  ']);\n        assert.equal(bufferService.buffer.x, 1);\n        await inputHandler.parseP(ttyBS);\n        assert.deepEqual(getLines(bufferService, 2), ['    ', '  ']);\n        assert.equal(bufferService.buffer.x, 0);\n      });\n    });\n  });\n\n  describe('reset text attributes (SGR 0)', () => {\n    it('resets all attributes if there is no url', async () => {\n      await inputHandler.parseP('\\x1b[30m\\x1b[40m\\x1b[4m');\n      assert.notEqual(inputHandler.curAttrData.fg, 0);\n      assert.notEqual(inputHandler.curAttrData.bg, 0);\n      assert.isFalse(inputHandler.curAttrData.extended.isEmpty());\n\n      await inputHandler.parseP('\\x1b[m');\n      assert.equal(inputHandler.curAttrData.fg, 0);\n      assert.equal(inputHandler.curAttrData.bg, 0);\n      assert.isTrue(inputHandler.curAttrData.extended.isEmpty());\n    });\n\n    it('resets all attributes except for the url', async () => {\n      await inputHandler.parseP('\\x1b[30m\\x1b[40m\\x1b[4m');\n      await inputHandler.parseP('\\x1b]8;;http://example.com\\x1b\\\\');\n      assert.notEqual(inputHandler.curAttrData.fg, 0);\n      assert.notEqual(inputHandler.curAttrData.bg, 0);\n      assert.notEqual(inputHandler.curAttrData.extended.ext, 0);\n      const urlId = inputHandler.curAttrData.extended.urlId;\n      assert.notEqual(urlId, 0);\n\n      await inputHandler.parseP('\\x1b[m');\n      assert.equal(inputHandler.curAttrData.fg, 0);\n      assert.equal(inputHandler.curAttrData.bg, BgFlags.HAS_EXTENDED);\n      const expectedExtended = new ExtendedAttrs();\n      expectedExtended.urlId = urlId;\n      assert.deepEqual(inputHandler.curAttrData.extended, expectedExtended);\n    });\n  });\n\n  describe('extended underline style support (SGR 4)', () => {\n    beforeEach(() => {\n      bufferService.resize(10, 5);\n    });\n    it('4 | 24', async () => {\n      await inputHandler.parseP('\\x1b[4m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('21 | 24', async () => {\n      await inputHandler.parseP('\\x1b[21m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('4:1 | 4:0', async () => {\n      await inputHandler.parseP('\\x1b[4:1m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);\n      await inputHandler.parseP('\\x1b[4:0m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n      await inputHandler.parseP('\\x1b[4:1m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('4:2 | 4:0', async () => {\n      await inputHandler.parseP('\\x1b[4:2m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE);\n      await inputHandler.parseP('\\x1b[4:0m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n      await inputHandler.parseP('\\x1b[4:2m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('4:3 | 4:0', async () => {\n      await inputHandler.parseP('\\x1b[4:3m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.CURLY);\n      await inputHandler.parseP('\\x1b[4:0m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n      await inputHandler.parseP('\\x1b[4:3m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.CURLY);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('4:4 | 4:0', async () => {\n      await inputHandler.parseP('\\x1b[4:4m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOTTED);\n      await inputHandler.parseP('\\x1b[4:0m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n      await inputHandler.parseP('\\x1b[4:4m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOTTED);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('4:5 | 4:0', async () => {\n      await inputHandler.parseP('\\x1b[4:5m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED);\n      await inputHandler.parseP('\\x1b[4:0m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n      await inputHandler.parseP('\\x1b[4:5m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED);\n      await inputHandler.parseP('\\x1b[24m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('4:x --> 4 should revert to single underline', async () => {\n      await inputHandler.parseP('\\x1b[4:5m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED);\n      await inputHandler.parseP('\\x1b[4m');\n      assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);\n    });\n  });\n  describe('underline colors (SGR 58 & SGR 59)', () => {\n    beforeEach(() => {\n      bufferService.resize(10, 5);\n    });\n    it('defaults to FG color', async () => {\n      for (const s of ['', '\\x1b[30m', '\\x1b[38;510m', '\\x1b[38;2;1;2;3m']) {\n        await inputHandler.parseP(s);\n        assert.equal(inputHandler.curAttrData.getUnderlineColor(), inputHandler.curAttrData.getFgColor());\n        assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode());\n        assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB());\n        assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette());\n        assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault());\n      }\n    });\n    it('correctly sets P256/RGB colors', async () => {\n      await inputHandler.parseP('\\x1b[4m');\n      await inputHandler.parseP('\\x1b[58;5;123m');\n      assert.equal(inputHandler.curAttrData.getUnderlineColor(), 123);\n      assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_P256);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), false);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), true);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);\n      await inputHandler.parseP('\\x1b[58;2::1:2:3m');\n      assert.equal(inputHandler.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);\n      assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), true);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), false);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);\n    });\n    it('P256/RGB persistence', async () => {\n      const cell = new CellData();\n      await inputHandler.parseP('\\x1b[4m');\n      await inputHandler.parseP('\\x1b[58;5;123m');\n      assert.equal(inputHandler.curAttrData.getUnderlineColor(), 123);\n      assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_P256);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), false);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), true);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);\n      await inputHandler.parseP('ab');\n      bufferService.buffer!.lines.get(0)!.loadCell(1, cell);\n      assert.equal(cell.getUnderlineColor(), 123);\n      assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256);\n      assert.equal(cell.isUnderlineColorRGB(), false);\n      assert.equal(cell.isUnderlineColorPalette(), true);\n      assert.equal(cell.isUnderlineColorDefault(), false);\n\n      await inputHandler.parseP('\\x1b[4:0m');\n      assert.equal(inputHandler.curAttrData.getUnderlineColor(), inputHandler.curAttrData.getFgColor());\n      assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode());\n      assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB());\n      assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette());\n      assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault());\n      await inputHandler.parseP('a');\n      bufferService.buffer!.lines.get(0)!.loadCell(1, cell);\n      assert.equal(cell.getUnderlineColor(), 123);\n      assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256);\n      assert.equal(cell.isUnderlineColorRGB(), false);\n      assert.equal(cell.isUnderlineColorPalette(), true);\n      assert.equal(cell.isUnderlineColorDefault(), false);\n      bufferService.buffer!.lines.get(0)!.loadCell(2, cell);\n      assert.equal(cell.getUnderlineColor(), inputHandler.curAttrData.getFgColor());\n      assert.equal(cell.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode());\n      assert.equal(cell.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB());\n      assert.equal(cell.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette());\n      assert.equal(cell.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault());\n\n      await inputHandler.parseP('\\x1b[4m');\n      await inputHandler.parseP('\\x1b[58;2::1:2:3m');\n      assert.equal(inputHandler.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);\n      assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), true);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), false);\n      assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);\n      await inputHandler.parseP('a');\n      await inputHandler.parseP('\\x1b[24m');\n      bufferService.buffer!.lines.get(0)!.loadCell(1, cell);\n      assert.equal(cell.getUnderlineColor(), 123);\n      assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256);\n      assert.equal(cell.isUnderlineColorRGB(), false);\n      assert.equal(cell.isUnderlineColorPalette(), true);\n      assert.equal(cell.isUnderlineColorDefault(), false);\n      bufferService.buffer!.lines.get(0)!.loadCell(3, cell);\n      assert.equal(cell.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);\n      assert.equal(cell.getUnderlineColorMode(), Attributes.CM_RGB);\n      assert.equal(cell.isUnderlineColorRGB(), true);\n      assert.equal(cell.isUnderlineColorPalette(), false);\n      assert.equal(cell.isUnderlineColorDefault(), false);\n\n      // eAttrs in buffer pos 0 and 1 should be the same object\n      assert.equal(\n        (bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[0],\n        (bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[1]\n      );\n      // should not have written eAttr for pos 2 in the buffer\n      assert.equal((bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[2], undefined);\n      // eAttrs in buffer pos 1 and pos 3 must be different objs\n      assert.notEqual(\n        (bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[1],\n        (bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[3]\n      );\n    });\n  });\n  describe('DECSTR', () => {\n    beforeEach(async () => {\n      bufferService.resize(10, 5);\n      optionsService.options.scrollback = 1;\n      await inputHandler.parseP('01234567890123');\n    });\n    it('should reset IRM', async () => {\n      await inputHandler.parseP('\\x1b[4h');\n      assert.equal(coreService.modes.insertMode, true);\n      await inputHandler.parseP('\\x1b[!p');\n      assert.equal(coreService.modes.insertMode, false);\n    });\n    it('should reset cursor visibility', async () => {\n      await inputHandler.parseP('\\x1b[?25l');\n      assert.equal(coreService.isCursorHidden, true);\n      await inputHandler.parseP('\\x1b[!p');\n      assert.equal(coreService.isCursorHidden, false);\n    });\n    it('should reset scroll margins', async () => {\n      await inputHandler.parseP('\\x1b[2;4r');\n      assert.equal(bufferService.buffer.scrollTop, 1);\n      assert.equal(bufferService.buffer.scrollBottom, 3);\n      await inputHandler.parseP('\\x1b[!p');\n      assert.equal(bufferService.buffer.scrollTop, 0);\n      assert.equal(bufferService.buffer.scrollBottom, bufferService.rows - 1);\n    });\n    it('should reset text attributes', async () => {\n      await inputHandler.parseP('\\x1b[1;2;32;43m');\n      assert.equal(!!inputHandler.curAttrData.isBold(), true);\n      await inputHandler.parseP('\\x1b[!p');\n      assert.equal(!!inputHandler.curAttrData.isBold(), false);\n      assert.equal(inputHandler.curAttrData.fg, 0);\n      assert.equal(inputHandler.curAttrData.bg, 0);\n    });\n    it('should reset DECSC data', async () => {\n      await inputHandler.parseP('\\x1b7');\n      assert.equal(bufferService.buffer.savedX, 4);\n      assert.equal(bufferService.buffer.savedY, 1);\n      await inputHandler.parseP('\\x1b[!p');\n      assert.equal(bufferService.buffer.savedX, 0);\n      assert.equal(bufferService.buffer.savedY, 0);\n    });\n    it('should reset DECOM', async () => {\n      await inputHandler.parseP('\\x1b[?6h');\n      assert.equal(coreService.decPrivateModes.origin, true);\n      await inputHandler.parseP('\\x1b[!p');\n      assert.equal(coreService.decPrivateModes.origin, false);\n    });\n  });\n  describe('OSC', () => {\n    it('4: query color events', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      // single color query\n      await inputHandler.parseP('\\x1b]4;0;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }]]);\n      stack.length = 0;\n      await inputHandler.parseP('\\x1b]4;123;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 123 }]]);\n      stack.length = 0;\n      // multiple queries\n      await inputHandler.parseP('\\x1b]4;0;?;123;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }, { type: ColorRequestType.REPORT, index: 123 }]]);\n      stack.length = 0;\n    });\n    it('4: set color events', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      // single color query\n      await inputHandler.parseP('\\x1b]4;0;rgb:01/02/03\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [1, 2, 3] }]]);\n      stack.length = 0;\n      await inputHandler.parseP('\\x1b]4;123;#aabbcc\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 123, color: [170, 187, 204] }]]);\n      stack.length = 0;\n      // multiple queries\n      await inputHandler.parseP('\\x1b]4;0;rgb:aa/bb/cc;123;#001122\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);\n      stack.length = 0;\n    });\n    it('4: should ignore invalid values', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      await inputHandler.parseP('\\x1b]4;0;rgb:aa/bb/cc;45;rgb:1/22/333;123;#001122\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);\n      stack.length = 0;\n    });\n    it('8: hyperlink with id', async () => {\n      await inputHandler.parseP('\\x1b]8;id=100;http://localhost:3000\\x07');\n      assert.notStrictEqual(inputHandler.curAttrData.extended.urlId, 0);\n      assert.deepStrictEqual(\n        oscLinkService.getLinkData(inputHandler.curAttrData.extended.urlId),\n        {\n          id: '100',\n          uri: 'http://localhost:3000'\n        }\n      );\n      await inputHandler.parseP('\\x1b]8;;\\x07');\n      assert.strictEqual(inputHandler.curAttrData.extended.urlId, 0);\n    });\n    it('8: hyperlink with semi-colon', async () => {\n      await inputHandler.parseP('\\x1b]8;;http://localhost:3000;abc=def\\x07');\n      assert.notStrictEqual(inputHandler.curAttrData.extended.urlId, 0);\n      assert.deepStrictEqual(\n        oscLinkService.getLinkData(inputHandler.curAttrData.extended.urlId),\n        {\n          id: undefined,\n          uri: 'http://localhost:3000;abc=def'\n        }\n      );\n      await inputHandler.parseP('\\x1b]8;;\\x07');\n      assert.strictEqual(inputHandler.curAttrData.extended.urlId, 0);\n    });\n    it('104: restore events', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      await inputHandler.parseP('\\x1b]104;0\\x07\\x1b]104;43\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }], [{ type: ColorRequestType.RESTORE, index: 43 }]]);\n      stack.length = 0;\n      // multiple in one command\n      await inputHandler.parseP('\\x1b]104;0;43\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }, { type: ColorRequestType.RESTORE, index: 43 }]]);\n      stack.length = 0;\n      // full ANSI table restore\n      await inputHandler.parseP('\\x1b]104\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE}]]);\n    });\n\n    it('10: FG set & query events', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      // single foreground query --> color undefined\n      await inputHandler.parseP('\\x1b]10;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.FOREGROUND }]]);\n      stack.length = 0;\n      // OSC with multiple values maps to OSC 10 & OSC 11 & OSC 12\n      await inputHandler.parseP('\\x1b]10;?;?;?;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.FOREGROUND }], [{ type: ColorRequestType.REPORT, index: SpecialColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);\n      stack.length = 0;\n      // set foreground color events\n      await inputHandler.parseP('\\x1b]10;rgb:01/02/03\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.FOREGROUND, color: [1, 2, 3] }]]);\n      stack.length = 0;\n      await inputHandler.parseP('\\x1b]10;#aabbcc\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.FOREGROUND, color: [170, 187, 204] }]]);\n      stack.length = 0;\n      // set FG, BG and cursor color at once\n      await inputHandler.parseP('\\x1b]10;rgb:aa/bb/cc;#001122;rgb:12/34/56\\x07');\n      assert.deepEqual(stack, [\n        [{ type: ColorRequestType.SET, index: SpecialColorIndex.FOREGROUND, color: [170, 187, 204] }],\n        [{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [0, 17, 34] }],\n        [{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [18, 52, 86] }]\n      ]);\n    });\n    it('110: restore FG color', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      await inputHandler.parseP('\\x1b]110\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.FOREGROUND }]]);\n    });\n    it('11: BG set & query events', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      // single background query --> color undefined\n      await inputHandler.parseP('\\x1b]11;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.BACKGROUND }]]);\n      stack.length = 0;\n      // OSC 11 with multiple values creates only BG and cursor event\n      await inputHandler.parseP('\\x1b]11;?;?;?;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);\n      stack.length = 0;\n      // set background color events\n      await inputHandler.parseP('\\x1b]11;rgb:01/02/03\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [1, 2, 3] }]]);\n      stack.length = 0;\n      await inputHandler.parseP('\\x1b]11;#aabbcc\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [170, 187, 204] }]]);\n      stack.length = 0;\n      // set BG and cursor color at once\n      await inputHandler.parseP('\\x1b]11;#001122;rgb:12/34/56\\x07');\n      assert.deepEqual(stack, [\n        [{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [0, 17, 34] }],\n        [{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [18, 52, 86] }]\n      ]);\n    });\n    it('111: restore BG color', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      await inputHandler.parseP('\\x1b]111\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.BACKGROUND }]]);\n    });\n    it('12: cursor color set & query events', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      // single cursor query --> color undefined\n      await inputHandler.parseP('\\x1b]12;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);\n      stack.length = 0;\n      // OSC 12 with multiple values creates only cursor event\n      await inputHandler.parseP('\\x1b]12;?;?;?;?\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);\n      stack.length = 0;\n      // set cursor color events\n      await inputHandler.parseP('\\x1b]12;rgb:01/02/03\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [1, 2, 3] }]]);\n      stack.length = 0;\n      await inputHandler.parseP('\\x1b]12;#aabbcc\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [170, 187, 204] }]]);\n    });\n    it('112: restore cursor color', async () => {\n      const stack: IColorEvent[] = [];\n      inputHandler.onColor(ev => stack.push(ev));\n      await inputHandler.parseP('\\x1b]112\\x07');\n      assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.CURSOR }]]);\n    });\n  });\n\n  // issue #3362 and #2979\n  describe('EL/ED cursor at buffer.cols', () => {\n    beforeEach(() => {\n      bufferService.resize(10, 5);\n    });\n    describe('cursor should stay at cols / does not overflow', () => {\n      it('EL0', async () => {\n        await inputHandler.parseP('##########\\x1b[0K');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);\n      });\n      it('EL1', async () => {\n        await inputHandler.parseP('##########\\x1b[1K');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);\n      });\n      it('EL2', async () => {\n        await inputHandler.parseP('##########\\x1b[2K');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);\n      });\n      it('ED0', async () => {\n        await inputHandler.parseP('##########\\x1b[0J');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);\n      });\n      it('ED1', async () => {\n        await inputHandler.parseP('##########\\x1b[1J');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);\n      });\n      it('ED2', async () => {\n        await inputHandler.parseP('##########\\x1b[2J');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);\n      });\n      it('ED3', async () => {\n        await inputHandler.parseP('##########\\x1b[3J');\n        assert.equal(bufferService.buffer.x, 10);\n        assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);\n      });\n    });\n    describe('following sequence keeps working', () => {\n      // sequences to test (cursor related ones)\n      const SEQ = [\n        /* ICH */   '\\x1b[10@',\n        /* SL */    '\\x1b[10 @',\n        /* CUU */   '\\x1b[10A',\n        /* SR */    '\\x1b[10 A',\n        /* CUD */   '\\x1b[10B',\n        /* CUF */   '\\x1b[10C',\n        /* CUB */   '\\x1b[10D',\n        /* CNL */   '\\x1b[10E',\n        /* CPL */   '\\x1b[10F',\n        /* CHA */   '\\x1b[10G',\n        /* CUP */   '\\x1b[10;10H',\n        /* CHT */   '\\x1b[10I',\n        /* IL */    '\\x1b[10L',\n        /* DL */    '\\x1b[10M',\n        /* DCH */   '\\x1b[10P',\n        /* SU */    '\\x1b[10S',\n        /* SD */    '\\x1b[10T',\n        /* ECH */   '\\x1b[10X',\n        /* CBT */   '\\x1b[10Z',\n        /* HPA */   '\\x1b[10`',\n        /* HPR */   '\\x1b[10a',\n        /* REP */   '\\x1b[10b',\n        /* VPA */   '\\x1b[10d',\n        /* VPR */   '\\x1b[10e',\n        /* HVP */   '\\x1b[10;10f',\n        /* TBC */   '\\x1b[0g',\n        /* SCOSC */ '\\x1b[s',\n        /* DECIC */ '\\x1b[10\\'}',\n        /* DECDC */ '\\x1b[10\\'~'\n      ];\n      it('cursor never advances beyond cols', async () => {\n        for (const seq of SEQ) {\n          await inputHandler.parseP('##########\\x1b[2J' + seq);\n          assert.equal(bufferService.buffer.x <= bufferService.cols, true);\n          inputHandler.reset();\n          bufferService.reset();\n        }\n      });\n    });\n  });\n\n  describe('DECSCA and DECSED/DECSEL', () => {\n    it('default is unprotected', async () => {\n      await inputHandler.parseP('some text');\n      await inputHandler.parseP('\\x1b[?2K');\n      assert.deepEqual(getLines(bufferService, 2), ['', '']);\n      await inputHandler.parseP('some text');\n      await inputHandler.parseP('\\x1b[?2J');\n      assert.deepEqual(getLines(bufferService, 2), ['', '']);\n    });\n    it('DECSCA 1 with DECSEL', async () => {\n      await inputHandler.parseP('###\\x1b[1\"qlineerase\\x1b[0\"q***');\n      await inputHandler.parseP('\\x1b[?2K');\n      assert.deepEqual(getLines(bufferService, 2), ['   lineerase', '']);\n      // normal EL works as before\n      await inputHandler.parseP('\\x1b[2K');\n      assert.deepEqual(getLines(bufferService, 2), ['', '']);\n    });\n    it('DECSCA 1 with DECSED', async () => {\n      await inputHandler.parseP('###\\x1b[1\"qdisplayerase\\x1b[0\"q***');\n      await inputHandler.parseP('\\x1b[?2J');\n      assert.deepEqual(getLines(bufferService, 2), ['   displayerase', '']);\n      // normal ED works as before\n      await inputHandler.parseP('\\x1b[2J');\n      assert.deepEqual(getLines(bufferService, 2), ['', '']);\n    });\n    it('DECRQSS reports correct DECSCA state', async () => {\n      const sendStack: string[] = [];\n      coreService.onData(d => sendStack.push(d));\n      // DCS $ q \" q ST\n      await inputHandler.parseP('\\x1bP$q\"q\\x1b\\\\');\n      // default - DECSCA unset (0 or 2)\n      assert.deepEqual(sendStack.pop(), '\\x1bP1$r0\"q\\x1b\\\\');\n      // DECSCA 1 - protected set\n      await inputHandler.parseP('###\\x1b[1\"q');\n      await inputHandler.parseP('\\x1bP$q\"q\\x1b\\\\');\n      assert.deepEqual(sendStack.pop(), '\\x1bP1$r1\"q\\x1b\\\\');\n      // DECSCA 2 - protected reset (same as 0)\n      await inputHandler.parseP('###\\x1b[2\"q');\n      await inputHandler.parseP('\\x1bP$q\"q\\x1b\\\\');\n      assert.deepEqual(sendStack.pop(), '\\x1bP1$r0\"q\\x1b\\\\');  // reported as DECSCA 0\n    });\n  });\n  describe('DECRQM', () => {\n    const reportStack: string[] = [];\n    beforeEach(() => {\n      reportStack.length = 0;\n      coreService.onData(data => reportStack.push(data));\n    });\n    it('ANSI 2 (keyboard action mode)', async () => {\n      await inputHandler.parseP('\\x1b[2$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[2;4$y');    // always reset\n    });\n    it('ANSI 4 (insert mode)', async () => {\n      await inputHandler.parseP('\\x1b[4$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[4;2$y');    // reset by default\n      await inputHandler.parseP('\\x1b[4h');\n      await inputHandler.parseP('\\x1b[4$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[4;1$y');    // now active\n      await inputHandler.parseP('\\x1b[4l');\n      await inputHandler.parseP('\\x1b[4$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[4;2$y');    // again reset\n    });\n    it('ANSI 12 (send/receive)', async () => {\n      await inputHandler.parseP('\\x1b[12$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[12;3$y');   // always set\n    });\n    it('ANSI 20 (newline mode)', async () => {\n      await inputHandler.parseP('\\x1b[20$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[20;2$y');    // reset by default\n      await inputHandler.parseP('\\x1b[20h');\n      await inputHandler.parseP('\\x1b[20$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[20;1$y');    // now active\n      await inputHandler.parseP('\\x1b[20l');\n      await inputHandler.parseP('\\x1b[20$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[20;2$y');    // again reset\n    });\n    it('ANSI unknown', async () => {\n      await inputHandler.parseP('\\x1b[1234$p');\n      assert.deepEqual(reportStack.pop(), '\\x1b[1234;0$y');  // not recognized\n    });\n    it('DEC privates with set/reset semantic', async () => {\n      // initially reset\n      const reset = [1, 6, 9, 45, 66, 1000, 1002, 1003, 1004, 1006, 1016, 47, 1047, 1049, 2004, 2026];\n      for (const mode of reset) {\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};2$y`);   // initial reset\n        await inputHandler.parseP(`\\x1b[?${mode}h`);\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};1$y`);   // now active\n        await inputHandler.parseP(`\\x1b[?${mode}l`);\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};2$y`);   // again reset\n      }\n      // initially set\n      const set = [7, 25];\n      for (const mode of set) {\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};1$y`);   // initial set\n        await inputHandler.parseP(`\\x1b[?${mode}l`);\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};2$y`);   // now inactive\n        await inputHandler.parseP(`\\x1b[?${mode}h`);\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};1$y`);   // again set\n      }\n    });\n    it('DEC privates quirks', async () => {\n      // Cursor blink\n      const mode = 12;\n      await inputHandler.parseP(`\\x1b[?${mode}$p`);\n      assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};2$y`); // initial reset\n      await inputHandler.parseP(`\\x1b[?${mode}h`);\n      await inputHandler.parseP(`\\x1b[?${mode}$p`);\n      assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};2$y`); // still reset\n\n      optionsService.options.quirks.allowSetCursorBlink = true;\n      await inputHandler.parseP(`\\x1b[?${mode}h`);\n      await inputHandler.parseP(`\\x1b[?${mode}$p`);\n      assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};1$y`); // now active\n      await inputHandler.parseP(`\\x1b[?${mode}l`);\n      await inputHandler.parseP(`\\x1b[?${mode}$p`);\n      assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};2$y`);   // now inactive\n    });\n    it('DEC privates perma modes', async () => {\n      // [mode number, state value]\n      const perma = [[3, 0], [8, 3], [67, 4], [1005, 4], [1015, 4], [1048, 1]];\n      for (const [mode, value] of perma) {\n        await inputHandler.parseP(`\\x1b[?${mode}$p`);\n        assert.deepEqual(reportStack.pop(), `\\x1b[?${mode};${value}$y`);\n      }\n    });\n  });\n\n  describe('InputHandler - kitty keyboard', () => {\n    let bufferService: IBufferService;\n    let coreService: ICoreService;\n    let optionsService: MockOptionsService;\n    let inputHandler: TestInputHandler;\n\n    beforeEach(() => {\n      optionsService = new MockOptionsService({ vtExtensions: { kittyKeyboard: true } });\n      bufferService = new BufferService(optionsService, new MockLogService());\n      bufferService.resize(80, 30);\n      coreService = new CoreService(bufferService, new MockLogService(), optionsService);\n      inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n    });\n\n    describe('stack limit', () => {\n      it('should evict oldest entry when stack exceeds 16 entries', async () => {\n        for (let i = 1; i <= 20; i++) {\n          await inputHandler.parseP(`\\x1b[>${i}u`);\n        }\n        assert.strictEqual(coreService.kittyKeyboard.mainStack.length, 16);\n        assert.strictEqual(coreService.kittyKeyboard.mainStack[0], 4);\n      });\n    });\n\n    describe('buffer switch', () => {\n      it('should maintain separate flags for main and alt screens', async () => {\n        await inputHandler.parseP('\\x1b[>5u');\n        assert.strictEqual(coreService.kittyKeyboard.flags, 5);\n        await inputHandler.parseP('\\x1b[?1049h');\n        assert.strictEqual(coreService.kittyKeyboard.flags, 0);\n        assert.strictEqual(coreService.kittyKeyboard.mainFlags, 5);\n        await inputHandler.parseP('\\x1b[>7u');\n        assert.strictEqual(coreService.kittyKeyboard.flags, 7);\n        await inputHandler.parseP('\\x1b[?1049l');\n        assert.strictEqual(coreService.kittyKeyboard.flags, 5);\n        assert.strictEqual(coreService.kittyKeyboard.altFlags, 7);\n      });\n    });\n\n    describe('pop reset', () => {\n      it('should reset flags to 0 when stack is emptied', async () => {\n        await inputHandler.parseP('\\x1b[>5u');\n        assert.strictEqual(coreService.kittyKeyboard.flags, 5);\n        await inputHandler.parseP('\\x1b[<10u');\n        assert.strictEqual(coreService.kittyKeyboard.flags, 0);\n      });\n    });\n  });\n\n\n  describe('InputHandler - async handlers', () => {\n    let bufferService: IBufferService;\n    let coreService: ICoreService;\n    let optionsService: MockOptionsService;\n    let inputHandler: TestInputHandler;\n\n    beforeEach(() => {\n      optionsService = new MockOptionsService();\n      bufferService = new BufferService(optionsService, new MockLogService());\n      bufferService.resize(80, 30);\n      coreService = new CoreService(bufferService, new MockLogService(), optionsService);\n      coreService.onData(data => { console.log(data); });\n\n      inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());\n    });\n\n    it('async CUP with CPR check', async () => {\n      const cup: number[][] = [];\n      const cpr: number[][] = [];\n      inputHandler.registerCsiHandler({ final: 'H' }, async params => {\n        cup.push(params.toArray() as number[]);\n        await Promise.resolve();\n        // late call of real repositioning\n        return inputHandler.cursorPosition(params);\n      });\n      coreService.onData(data => {\n        const m = data.match(/\\x1b\\[(.*?);(.*?)R/);\n        if (m) {\n          cpr.push([parseInt(m[1]), parseInt(m[2])]);\n        }\n      });\n      await inputHandler.parseP('aaa\\x1b[3;4H\\x1b[6nbbb\\x1b[6;8H\\x1b[6n');\n      assert.deepEqual(cup, cpr);\n    });\n    it('async OSC between', async () => {\n      inputHandler.registerOscHandler(1000, async data => {\n        await Promise.resolve();\n        assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']);\n        assert.equal(data, 'some data');\n        return true;\n      });\n      await inputHandler.parseP('hello world!\\r\\n\\x1b]1000;some data\\x07second line');\n      assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']);\n    });\n    it('async DCS between', async () => {\n      inputHandler.registerDcsHandler({ final: 'a' }, async (data, params) => {\n        await Promise.resolve();\n        assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']);\n        assert.equal(data, 'some data');\n        assert.deepEqual(params.toArray(), [1, 2]);\n        return true;\n      });\n      await inputHandler.parseP('hello world!\\r\\n\\x1bP1;2asome data\\x1b\\\\second line');\n      assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/InputHandler.ts",
    "content": "/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n */\n\nimport { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType, SpecialColorIndex } from 'common/Types';\nimport { C0, C1 } from 'common/data/EscapeSequences';\nimport { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';\nimport { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';\nimport { Disposable } from 'common/Lifecycle';\nimport { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder';\nimport { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';\nimport { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants';\nimport { CellData } from 'common/buffer/CellData';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { ICoreService, IBufferService, IOptionsService, ILogService, IMouseStateService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services';\nimport { UnicodeService } from 'common/services/UnicodeService';\nimport { OscHandler } from 'common/parser/OscParser';\nimport { DcsHandler } from 'common/parser/DcsParser';\nimport { ApcHandler } from 'common/parser/ApcParser';\nimport { IBuffer } from 'common/buffer/Types';\nimport { parseColor } from 'common/input/XParseColor';\nimport { Emitter } from 'common/Event';\nimport { XTERM_VERSION } from 'common/Version';\n\n/**\n * Map collect to glevel. Used in `selectCharset`.\n */\nconst GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };\n\n/**\n * VT commands done by the parser - FIXME: move this to the parser?\n */\n// @vt: #Y   ESC   CSI   \"Control Sequence Introducer\"   \"ESC [\"   \"Start of a CSI sequence.\"\n// @vt: #Y   ESC   OSC   \"Operating System Command\"      \"ESC ]\"   \"Start of an OSC sequence.\"\n// @vt: #Y   ESC   DCS   \"Device Control String\"         \"ESC P\"   \"Start of a DCS sequence.\"\n// @vt: #Y   ESC   ST    \"String Terminator\"             \"ESC \\\"   \"Terminator used for string type sequences.\"\n// @vt: #Y   ESC   PM    \"Privacy Message\"               \"ESC ^\"   \"Start of a privacy message.\"\n// @vt: #Y   ESC   APC   \"Application Program Command\"   \"ESC _\"   \"Start of an APC sequence.\"\n// @vt: #Y   C1    CSI   \"Control Sequence Introducer\"   \"\\x9B\"    \"Start of a CSI sequence.\"\n// @vt: #Y   C1    OSC   \"Operating System Command\"      \"\\x9D\"    \"Start of an OSC sequence.\"\n// @vt: #Y   C1    DCS   \"Device Control String\"         \"\\x90\"    \"Start of a DCS sequence.\"\n// @vt: #Y   C1    ST    \"String Terminator\"             \"\\x9C\"    \"Terminator used for string type sequences.\"\n// @vt: #Y   C1    PM    \"Privacy Message\"               \"\\x9E\"    \"Start of a privacy message.\"\n// @vt: #Y   C1    APC   \"Application Program Command\"   \"\\x9F\"    \"Start of an APC sequence.\"\n// @vt: #Y   C0    NUL   \"Null\"                          \"\\0, \\x00\"  \"NUL is ignored.\"\n// @vt: #Y   C0    ESC   \"Escape\"                        \"\\e, \\x1B\"  \"Start of a sequence. Cancels any other sequence.\"\n\n/**\n * Document xterm VT features here that are currently unsupported\n */\n// @vt: #E[Supported via @xterm/addon-image.]  DCS   SIXEL       \"SIXEL Graphics\"          \"DCS Ps ; Ps ; Ps ; q \tPt ST\"  \"Draw SIXEL image.\"\n// @vt: #N  DCS   DECUDK      \"User Defined Keys\"       \"DCS Ps ; Ps \\| Pt ST\"           \"Definitions for user-defined keys.\"\n// @vt: #N  DCS   XTGETTCAP   \"Request Terminfo String\" \"DCS + q Pt ST\"                 \"Request Terminfo String.\"\n// @vt: #N  DCS   XTSETTCAP   \"Set Terminfo Data\"       \"DCS + p Pt ST\"                 \"Set Terminfo Data.\"\n// @vt: #N  OSC   1           \"Set Icon Name\"           \"OSC 1 ; Pt BEL\"                \"Set icon name.\"\n\n/**\n * Max length of the UTF32 input buffer. Real memory consumption is 4 times higher.\n */\nconst MAX_PARSEBUFFER_LENGTH = 131072;\n\n/**\n * Limit length of title and icon name stacks.\n */\nconst STACK_LIMIT = 10;\n\n// map params to window option\nfunction paramToWindowOption(n: number, opts: IWindowOptions): boolean {\n  if (n > 24) {\n    return opts.setWinLines || false;\n  }\n  switch (n) {\n    case 1: return !!opts.restoreWin;\n    case 2: return !!opts.minimizeWin;\n    case 3: return !!opts.setWinPosition;\n    case 4: return !!opts.setWinSizePixels;\n    case 5: return !!opts.raiseWin;\n    case 6: return !!opts.lowerWin;\n    case 7: return !!opts.refreshWin;\n    case 8: return !!opts.setWinSizeChars;\n    case 9: return !!opts.maximizeWin;\n    case 10: return !!opts.fullscreenWin;\n    case 11: return !!opts.getWinState;\n    case 13: return !!opts.getWinPosition;\n    case 14: return !!opts.getWinSizePixels;\n    case 15: return !!opts.getScreenSizePixels;\n    case 16: return !!opts.getCellSizePixels;\n    case 18: return !!opts.getWinSizeChars;\n    case 19: return !!opts.getScreenSizeChars;\n    case 20: return !!opts.getIconTitle;\n    case 21: return !!opts.getWinTitle;\n    case 22: return !!opts.pushTitle;\n    case 23: return !!opts.popTitle;\n    case 24: return !!opts.setWinLines;\n  }\n  return false;\n}\n\nexport enum WindowsOptionsReportType {\n  GET_WIN_SIZE_PIXELS = 0,\n  GET_CELL_SIZE_PIXELS = 1\n}\n\n// create a warning log if an async handler takes longer than the limit (in ms)\nconst SLOW_ASYNC_LIMIT = 5000;\n\n// Work variables to avoid garbage collection\nlet $temp = 0;\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler extends Disposable implements IInputHandler {\n  private _parseBuffer: Uint32Array = new Uint32Array(4096);\n  private _stringDecoder: StringToUtf32 = new StringToUtf32();\n  private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32();\n  private _windowTitle = '';\n  private _iconName = '';\n  private _dirtyRowTracker: IDirtyRowTracker;\n  protected _windowTitleStack: string[] = [];\n  protected _iconNameStack: string[] = [];\n\n  private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone();\n  public getAttrData(): IAttributeData { return this._curAttrData; }\n  private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone();\n\n  private _activeBuffer: IBuffer;\n\n  private readonly _onRequestBell = this._register(new Emitter<void>());\n  public readonly onRequestBell = this._onRequestBell.event;\n  private readonly _onRequestRefreshRows = this._register(new Emitter<{ start: number, end: number } | undefined>());\n  public readonly onRequestRefreshRows = this._onRequestRefreshRows.event;\n  private readonly _onRequestReset = this._register(new Emitter<void>());\n  public readonly onRequestReset = this._onRequestReset.event;\n  private readonly _onRequestSendFocus = this._register(new Emitter<void>());\n  public readonly onRequestSendFocus = this._onRequestSendFocus.event;\n  private readonly _onRequestSyncScrollBar = this._register(new Emitter<void>());\n  public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event;\n  private readonly _onRequestWindowsOptionsReport = this._register(new Emitter<WindowsOptionsReportType>());\n  public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event;\n\n  private readonly _onA11yChar = this._register(new Emitter<string>());\n  public readonly onA11yChar = this._onA11yChar.event;\n  private readonly _onA11yTab = this._register(new Emitter<number>());\n  public readonly onA11yTab = this._onA11yTab.event;\n  private readonly _onCursorMove = this._register(new Emitter<void>());\n  public readonly onCursorMove = this._onCursorMove.event;\n  private readonly _onLineFeed = this._register(new Emitter<void>());\n  public readonly onLineFeed = this._onLineFeed.event;\n  private readonly _onScroll = this._register(new Emitter<number>());\n  public readonly onScroll = this._onScroll.event;\n  private readonly _onTitleChange = this._register(new Emitter<string>());\n  public readonly onTitleChange = this._onTitleChange.event;\n  private readonly _onColor = this._register(new Emitter<IColorEvent>());\n  public readonly onColor = this._onColor.event;\n  private readonly _onRequestColorSchemeQuery = this._register(new Emitter<void>());\n  public readonly onRequestColorSchemeQuery = this._onRequestColorSchemeQuery.event;\n\n  private _parseStack: IParseStack = {\n    paused: false,\n    cursorStartX: 0,\n    cursorStartY: 0,\n    decodedLength: 0,\n    position: 0\n  };\n\n  constructor(\n    private readonly _bufferService: IBufferService,\n    private readonly _charsetService: ICharsetService,\n    private readonly _coreService: ICoreService,\n    private readonly _logService: ILogService,\n    private readonly _optionsService: IOptionsService,\n    private readonly _oscLinkService: IOscLinkService,\n    private readonly _mouseStateService: IMouseStateService,\n    private readonly _unicodeService: IUnicodeService,\n    private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()\n  ) {\n    super();\n    this._register(this._parser);\n    this._dirtyRowTracker = new DirtyRowTracker(this._bufferService);\n\n    // Track properties used in performance critical code manually to avoid using slow getters\n    this._activeBuffer = this._bufferService.buffer;\n    this._register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));\n\n    /**\n     * custom fallback handlers\n     */\n    this._parser.setCsiHandlerFallback((ident, params) => {\n      this._logService.debug('Unknown CSI code: ', { identifier: this._parser.identToString(ident), params: params.toArray() });\n    });\n    this._parser.setEscHandlerFallback(ident => {\n      this._logService.debug('Unknown ESC code: ', { identifier: this._parser.identToString(ident) });\n    });\n    this._parser.setExecuteHandlerFallback(code => {\n      this._logService.debug('Unknown EXECUTE code: ', { code });\n    });\n    this._parser.setOscHandlerFallback((identifier, action, data) => {\n      this._logService.debug('Unknown OSC code: ', { identifier, action, data });\n    });\n    this._parser.setDcsHandlerFallback((ident, action, payload) => {\n      if (action === 'HOOK') {\n        payload = payload.toArray();\n      }\n      this._logService.debug('Unknown DCS code: ', { identifier: this._parser.identToString(ident), action, payload });\n    });\n\n    /**\n     * print handler\n     */\n    this._parser.setPrintHandler((data, start, end) => this.print(data, start, end));\n\n    /**\n     * CSI handler\n     */\n    this._parser.registerCsiHandler({ final: '@' }, params => this.insertChars(params));\n    this._parser.registerCsiHandler({ intermediates: ' ', final: '@' }, params => this.scrollLeft(params));\n    this._parser.registerCsiHandler({ final: 'A' }, params => this.cursorUp(params));\n    this._parser.registerCsiHandler({ intermediates: ' ', final: 'A' }, params => this.scrollRight(params));\n    this._parser.registerCsiHandler({ final: 'B' }, params => this.cursorDown(params));\n    this._parser.registerCsiHandler({ final: 'C' }, params => this.cursorForward(params));\n    this._parser.registerCsiHandler({ final: 'D' }, params => this.cursorBackward(params));\n    this._parser.registerCsiHandler({ final: 'E' }, params => this.cursorNextLine(params));\n    this._parser.registerCsiHandler({ final: 'F' }, params => this.cursorPrecedingLine(params));\n    this._parser.registerCsiHandler({ final: 'G' }, params => this.cursorCharAbsolute(params));\n    this._parser.registerCsiHandler({ final: 'H' }, params => this.cursorPosition(params));\n    this._parser.registerCsiHandler({ final: 'I' }, params => this.cursorForwardTab(params));\n    this._parser.registerCsiHandler({ final: 'J' }, params => this.eraseInDisplay(params, false));\n    this._parser.registerCsiHandler({ prefix: '?', final: 'J' }, params => this.eraseInDisplay(params, true));\n    this._parser.registerCsiHandler({ final: 'K' }, params => this.eraseInLine(params, false));\n    this._parser.registerCsiHandler({ prefix: '?', final: 'K' }, params => this.eraseInLine(params, true));\n    this._parser.registerCsiHandler({ final: 'L' }, params => this.insertLines(params));\n    this._parser.registerCsiHandler({ final: 'M' }, params => this.deleteLines(params));\n    this._parser.registerCsiHandler({ final: 'P' }, params => this.deleteChars(params));\n    this._parser.registerCsiHandler({ final: 'S' }, params => this.scrollUp(params));\n    this._parser.registerCsiHandler({ final: 'T' }, params => this.scrollDown(params));\n    this._parser.registerCsiHandler({ final: 'X' }, params => this.eraseChars(params));\n    this._parser.registerCsiHandler({ final: 'Z' }, params => this.cursorBackwardTab(params));\n    this._parser.registerCsiHandler({ final: '^' }, params => this.scrollDown(params));\n    this._parser.registerCsiHandler({ final: '`' }, params => this.charPosAbsolute(params));\n    this._parser.registerCsiHandler({ final: 'a' }, params => this.hPositionRelative(params));\n    this._parser.registerCsiHandler({ final: 'b' }, params => this.repeatPrecedingCharacter(params));\n    this._parser.registerCsiHandler({ final: 'c' }, params => this.sendDeviceAttributesPrimary(params));\n    this._parser.registerCsiHandler({ prefix: '>', final: 'c' }, params => this.sendDeviceAttributesSecondary(params));\n    this._parser.registerCsiHandler({ final: 'd' }, params => this.linePosAbsolute(params));\n    this._parser.registerCsiHandler({ final: 'e' }, params => this.vPositionRelative(params));\n    this._parser.registerCsiHandler({ final: 'f' }, params => this.hVPosition(params));\n    this._parser.registerCsiHandler({ final: 'g' }, params => this.tabClear(params));\n    this._parser.registerCsiHandler({ final: 'h' }, params => this.setMode(params));\n    this._parser.registerCsiHandler({ prefix: '?', final: 'h' }, params => this.setModePrivate(params));\n    this._parser.registerCsiHandler({ final: 'l' }, params => this.resetMode(params));\n    this._parser.registerCsiHandler({ prefix: '?', final: 'l' }, params => this.resetModePrivate(params));\n    this._parser.registerCsiHandler({ final: 'm' }, params => this.charAttributes(params));\n    this._parser.registerCsiHandler({ final: 'n' }, params => this.deviceStatus(params));\n    this._parser.registerCsiHandler({ prefix: '?', final: 'n' }, params => this.deviceStatusPrivate(params));\n    this._parser.registerCsiHandler({ intermediates: '!', final: 'p' }, params => this.softReset(params));\n    this._parser.registerCsiHandler({ prefix: '>', final: 'q' }, params => this.sendXtVersion(params));\n    this._parser.registerCsiHandler({ intermediates: ' ', final: 'q' }, params => this.setCursorStyle(params));\n    this._parser.registerCsiHandler({ final: 'r' }, params => this.setScrollRegion(params));\n    this._parser.registerCsiHandler({ final: 's' }, params => this.saveCursor(params));\n    this._parser.registerCsiHandler({ final: 't' }, params => this.windowOptions(params));\n    this._parser.registerCsiHandler({ final: 'u' }, params => this.restoreCursor(params));\n    this._parser.registerCsiHandler({ intermediates: '\\'', final: '}' }, params => this.insertColumns(params));\n    this._parser.registerCsiHandler({ intermediates: '\\'', final: '~' }, params => this.deleteColumns(params));\n    this._parser.registerCsiHandler({ intermediates: '\"', final: 'q' }, params => this.selectProtected(params));\n    this._parser.registerCsiHandler({ intermediates: '$', final: 'p' }, params => this.requestMode(params, true));\n    this._parser.registerCsiHandler({ prefix: '?', intermediates: '$', final: 'p' }, params => this.requestMode(params, false));\n\n    // Kitty keyboard protocol handlers\n    this._parser.registerCsiHandler({ prefix: '=', final: 'u' }, params => this.kittyKeyboardSet(params));\n    this._parser.registerCsiHandler({ prefix: '?', final: 'u' }, params => this.kittyKeyboardQuery(params));\n    this._parser.registerCsiHandler({ prefix: '>', final: 'u' }, params => this.kittyKeyboardPush(params));\n    this._parser.registerCsiHandler({ prefix: '<', final: 'u' }, params => this.kittyKeyboardPop(params));\n\n    /**\n     * execute handler\n     */\n    this._parser.setExecuteHandler(C0.BEL, () => this.bell());\n    this._parser.setExecuteHandler(C0.LF, () => this.lineFeed());\n    this._parser.setExecuteHandler(C0.VT, () => this.lineFeed());\n    this._parser.setExecuteHandler(C0.FF, () => this.lineFeed());\n    this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn());\n    this._parser.setExecuteHandler(C0.BS, () => this.backspace());\n    this._parser.setExecuteHandler(C0.HT, () => this.tab());\n    this._parser.setExecuteHandler(C0.SO, () => this.shiftOut());\n    this._parser.setExecuteHandler(C0.SI, () => this.shiftIn());\n    // FIXME:   What do to with missing? Old code just added those to print.\n\n    this._parser.setExecuteHandler(C1.IND, () => this.index());\n    this._parser.setExecuteHandler(C1.NEL, () => this.nextLine());\n    this._parser.setExecuteHandler(C1.HTS, () => this.tabSet());\n\n    /**\n     * OSC handler\n     */\n    //   0 - icon name + title\n    this._parser.registerOscHandler(0, new OscHandler(data => { this.setTitle(data); this.setIconName(data); return true; }));\n    //   1 - icon name\n    this._parser.registerOscHandler(1, new OscHandler(data => this.setIconName(data)));\n    //   2 - title\n    this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));\n    //   3 - set property X in the form \"prop=value\"\n    //   4 - Change Color Number\n    this._parser.registerOscHandler(4, new OscHandler(data => this.setOrReportIndexedColor(data)));\n    //   5 - Change Special Color Number\n    //   6 - Enable/disable Special Color Number c\n    //   7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)\n    //   8 - create hyperlink (not in xterm spec, see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)\n    this._parser.registerOscHandler(8, new OscHandler(data => this.setHyperlink(data)));\n    //  10 - Change VT100 text foreground color to Pt.\n    this._parser.registerOscHandler(10, new OscHandler(data => this.setOrReportFgColor(data)));\n    //  11 - Change VT100 text background color to Pt.\n    this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));\n    //  12 - Change text cursor color to Pt.\n    this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));\n    //  13 - Change mouse foreground color to Pt.\n    //  14 - Change mouse background color to Pt.\n    //  15 - Change Tektronix foreground color to Pt.\n    //  16 - Change Tektronix background color to Pt.\n    //  17 - Change highlight background color to Pt.\n    //  18 - Change Tektronix cursor color to Pt.\n    //  19 - Change highlight foreground color to Pt.\n    //  46 - Change Log File to Pt.\n    //  50 - Set Font to Pt.\n    //  51 - reserved for Emacs shell.\n    //  52 - Manipulate Selection Data.\n    // 104 ; c - Reset Color Number c.\n    this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data)));\n    // 105 ; c - Reset Special Color Number c.\n    // 106 ; c; f - Enable/disable Special Color Number c.\n    // 110 - Reset VT100 text foreground color.\n    this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data)));\n    // 111 - Reset VT100 text background color.\n    this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data)));\n    // 112 - Reset text cursor color.\n    this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data)));\n    // 113 - Reset mouse foreground color.\n    // 114 - Reset mouse background color.\n    // 115 - Reset Tektronix foreground color.\n    // 116 - Reset Tektronix background color.\n    // 117 - Reset highlight color.\n    // 118 - Reset Tektronix cursor color.\n    // 119 - Reset highlight foreground color.\n\n    /**\n     * ESC handlers\n     */\n    this._parser.registerEscHandler({ final: '7' }, () => this.saveCursor());\n    this._parser.registerEscHandler({ final: '8' }, () => this.restoreCursor());\n    this._parser.registerEscHandler({ final: 'D' }, () => this.index());\n    this._parser.registerEscHandler({ final: 'E' }, () => this.nextLine());\n    this._parser.registerEscHandler({ final: 'H' }, () => this.tabSet());\n    this._parser.registerEscHandler({ final: 'M' }, () => this.reverseIndex());\n    this._parser.registerEscHandler({ final: '=' }, () => this.keypadApplicationMode());\n    this._parser.registerEscHandler({ final: '>' }, () => this.keypadNumericMode());\n    this._parser.registerEscHandler({ final: 'c' }, () => this.fullReset());\n    this._parser.registerEscHandler({ final: 'n' }, () => this.setgLevel(2));\n    this._parser.registerEscHandler({ final: 'o' }, () => this.setgLevel(3));\n    this._parser.registerEscHandler({ final: '|' }, () => this.setgLevel(3));\n    this._parser.registerEscHandler({ final: '}' }, () => this.setgLevel(2));\n    this._parser.registerEscHandler({ final: '~' }, () => this.setgLevel(1));\n    this._parser.registerEscHandler({ intermediates: '%', final: '@' }, () => this.selectDefaultCharset());\n    this._parser.registerEscHandler({ intermediates: '%', final: 'G' }, () => this.selectDefaultCharset());\n    for (const flag in CHARSETS) {\n      this._parser.registerEscHandler({ intermediates: '(', final: flag }, () => this.selectCharset('(' + flag));\n      this._parser.registerEscHandler({ intermediates: ')', final: flag }, () => this.selectCharset(')' + flag));\n      this._parser.registerEscHandler({ intermediates: '*', final: flag }, () => this.selectCharset('*' + flag));\n      this._parser.registerEscHandler({ intermediates: '+', final: flag }, () => this.selectCharset('+' + flag));\n      this._parser.registerEscHandler({ intermediates: '-', final: flag }, () => this.selectCharset('-' + flag));\n      this._parser.registerEscHandler({ intermediates: '.', final: flag }, () => this.selectCharset('.' + flag));\n      this._parser.registerEscHandler({ intermediates: '/', final: flag }, () => this.selectCharset('/' + flag)); // TODO: supported?\n    }\n    this._parser.registerEscHandler({ intermediates: '#', final: '8' }, () => this.screenAlignmentPattern());\n\n    /**\n     * error handler\n     */\n    this._parser.setErrorHandler((state: IParsingState) => {\n      this._logService.error('Parsing error: ', state);\n      return state;\n    });\n\n    /**\n     * DCS handler\n     */\n    this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DcsHandler((data, params) => this.requestStatusString(data, params)));\n  }\n\n  /**\n   * Async parse support.\n   */\n  private _preserveStack(cursorStartX: number, cursorStartY: number, decodedLength: number, position: number): void {\n    this._parseStack.paused = true;\n    this._parseStack.cursorStartX = cursorStartX;\n    this._parseStack.cursorStartY = cursorStartY;\n    this._parseStack.decodedLength = decodedLength;\n    this._parseStack.position = position;\n  }\n\n  private _logSlowResolvingAsync(p: Promise<boolean>): void {\n    // log a limited warning about an async handler taking too long\n    if (this._logService.logLevel <= LogLevelEnum.WARN) {\n      let slowTimeout: ReturnType<typeof setTimeout> | undefined;\n      const slowPromise = new Promise<never>((_res, rej) => {\n        slowTimeout = setTimeout(() => rej('#SLOW_TIMEOUT'), SLOW_ASYNC_LIMIT);\n      });\n      Promise.race([p, slowPromise])\n        .then(() => {\n          if (slowTimeout !== undefined) {\n            clearTimeout(slowTimeout);\n          }\n        }, err => {\n          if (slowTimeout !== undefined) {\n            clearTimeout(slowTimeout);\n          }\n          if (err !== '#SLOW_TIMEOUT') {\n            throw err;\n          }\n          console.warn(`async parser handler taking longer than ${SLOW_ASYNC_LIMIT} ms`);\n        });\n    }\n  }\n\n  private _getCurrentLinkId(): number {\n    return this._curAttrData.extended.urlId;\n  }\n\n  /**\n   * Parse call with async handler support.\n   *\n   * Whether the stack state got preserved for the next call, is indicated by the return value:\n   * - undefined (void):\n   *   all handlers were sync, no stack save, continue normally with next chunk\n   * - Promise\\<boolean\\>:\n   *   execution stopped at async handler, stack saved, continue with same chunk and the promise\n   *   resolve value as `promiseResult` until the method returns `undefined`\n   *\n   * Note: This method should only be called by `Terminal.write` to ensure correct execution order\n   * and proper continuation of async parser handlers.\n   */\n  public parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise<boolean> {\n    let result: void | Promise<boolean>;\n    let cursorStartX = this._activeBuffer.x;\n    let cursorStartY = this._activeBuffer.y;\n    let start = 0;\n    const wasPaused = this._parseStack.paused;\n\n    if (wasPaused) {\n      // assumption: _parseBuffer never mutates between async calls\n      if (result = this._parser.parse(this._parseBuffer, this._parseStack.decodedLength, promiseResult)) {\n        this._logSlowResolvingAsync(result);\n        return result;\n      }\n      cursorStartX = this._parseStack.cursorStartX;\n      cursorStartY = this._parseStack.cursorStartY;\n      this._parseStack.paused = false;\n      if (data.length > MAX_PARSEBUFFER_LENGTH) {\n        start = this._parseStack.position + MAX_PARSEBUFFER_LENGTH;\n      }\n    }\n\n    // Log debug data, the log level gate is to prevent extra work in this hot path\n    if (this._logService.logLevel <= LogLevelEnum.DEBUG) {\n      this._logService.debug(`parsing data ${typeof data === 'string' ? ` \"${data}\"` : ` \"${Array.prototype.map.call(data, e => String.fromCharCode(e)).join('')}\"`}`);\n    }\n    if (this._logService.logLevel === LogLevelEnum.TRACE) {\n      this._logService.trace(`parsing data (codes)`, typeof data === 'string'\n        ? data.split('').map(e => e.charCodeAt(0))\n        : data\n      );\n    }\n\n    // resize input buffer if needed\n    if (this._parseBuffer.length < data.length) {\n      if (this._parseBuffer.length < MAX_PARSEBUFFER_LENGTH) {\n        this._parseBuffer = new Uint32Array(Math.min(data.length, MAX_PARSEBUFFER_LENGTH));\n      }\n    }\n\n    // Clear the dirty row service so we know which lines changed as a result of parsing\n    // Important: do not clear between async calls, otherwise we lost pending update information.\n    if (!wasPaused) {\n      this._dirtyRowTracker.clearRange();\n    }\n\n    // process big data in smaller chunks\n    if (data.length > MAX_PARSEBUFFER_LENGTH) {\n      for (let i = start; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {\n        const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length;\n        const len = (typeof data === 'string')\n          ? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer)\n          : this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer);\n        if (result = this._parser.parse(this._parseBuffer, len)) {\n          this._preserveStack(cursorStartX, cursorStartY, len, i);\n          this._logSlowResolvingAsync(result);\n          return result;\n        }\n      }\n    } else {\n      if (!wasPaused) {\n        const len = (typeof data === 'string')\n          ? this._stringDecoder.decode(data, this._parseBuffer)\n          : this._utf8Decoder.decode(data, this._parseBuffer);\n        if (result = this._parser.parse(this._parseBuffer, len)) {\n          this._preserveStack(cursorStartX, cursorStartY, len, 0);\n          this._logSlowResolvingAsync(result);\n          return result;\n        }\n      }\n    }\n\n    if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) {\n      this._onCursorMove.fire();\n    }\n\n    // Refresh any dirty rows accumulated as part of parsing, fire only for rows within the\n    // _viewport_ which is relative to ydisp, not relative to ybase.\n    const viewportEnd = this._dirtyRowTracker.end + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);\n    const viewportStart = this._dirtyRowTracker.start + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);\n    if (viewportStart < this._bufferService.rows) {\n      this._onRequestRefreshRows.fire({\n        start: Math.min(viewportStart, this._bufferService.rows - 1),\n        end: Math.min(viewportEnd, this._bufferService.rows - 1)\n      });\n    }\n  }\n\n  public print(data: Uint32Array, start: number, end: number): void {\n    let code: number;\n    let chWidth: number;\n    const charset = this._charsetService.charset;\n    const screenReaderMode = this._optionsService.rawOptions.screenReaderMode;\n    const cols = this._bufferService.cols;\n    const wraparoundMode = this._coreService.decPrivateModes.wraparound;\n    const insertMode = this._coreService.modes.insertMode;\n    const curAttr = this._curAttrData;\n    let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);\n\n    // Defensive check: bufferRow can be undefined if a resize occurred mid-write due to async\n    // scheduling gaps in WriteBuffer. See https://github.com/xtermjs/xterm.js/issues/5597\n    if (!bufferRow) {\n      return;\n    }\n\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n\n    // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char\n    if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) {\n      bufferRow.setCellFromCodepoint(this._activeBuffer.x - 1, 0, 1, curAttr);\n    }\n\n    let precedingJoinState = this._parser.precedingJoinState;\n    for (let pos = start; pos < end; ++pos) {\n      code = data[pos];\n\n      // Soft hyphen's (U+00AD) behavior is ambiguous and differs across terminals. We opt to treat\n      // it as a zero-width hint to text layout engines and simply ignore it.\n      if (code === 0xAD) {\n        continue;\n      }\n\n      // get charset replacement character\n      // charset is only defined for ASCII, therefore we only\n      // search for an replacement char if code < 127\n      if (code < 127 && charset) {\n        const ch = charset[String.fromCharCode(code)];\n        if (ch) {\n          code = ch.charCodeAt(0);\n        }\n      }\n\n      const currentInfo = this._unicodeService.charProperties(code, precedingJoinState);\n      chWidth = UnicodeService.extractWidth(currentInfo);\n      const shouldJoin = UnicodeService.extractShouldJoin(currentInfo);\n      const oldWidth = shouldJoin ? UnicodeService.extractWidth(precedingJoinState) : 0;\n      precedingJoinState = currentInfo;\n\n      if (screenReaderMode) {\n        this._onA11yChar.fire(stringFromCodePoint(code));\n      }\n      if (this._getCurrentLinkId()) {\n        this._oscLinkService.addLineToLink(this._getCurrentLinkId(), this._activeBuffer.ybase + this._activeBuffer.y);\n      }\n\n      // goto next line if ch would overflow\n      // NOTE: To avoid costly width checks here,\n      // the terminal does not allow a cols < 2.\n      if (this._activeBuffer.x + chWidth - oldWidth > cols) {\n        // autowrap - DECAWM\n        // automatically wraps to the beginning of the next line\n        if (wraparoundMode) {\n          const oldRow = bufferRow;\n          let oldCol = this._activeBuffer.x - oldWidth;\n          this._activeBuffer.x = oldWidth;\n          this._activeBuffer.y++;\n          if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {\n            this._activeBuffer.y--;\n            this._bufferService.scroll(this._eraseAttrData(), true);\n          } else {\n            if (this._activeBuffer.y >= this._bufferService.rows) {\n              this._activeBuffer.y = this._bufferService.rows - 1;\n            }\n            // The line already exists (eg. the initial viewport), mark it as a\n            // wrapped line\n            this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = true;\n          }\n          // row changed, get it again\n          bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);\n          if (!bufferRow) {\n            return;\n          }\n          if (oldWidth > 0 && bufferRow instanceof BufferLine) {\n            // Combining character widens 1 column to 2.\n            // Move old character to next line.\n            bufferRow.copyCellsFrom(oldRow as BufferLine,\n              oldCol, 0, oldWidth, false);\n          }\n          // clear left over cells to the right\n          while (oldCol < cols) {\n            oldRow.setCellFromCodepoint(oldCol++, 0, 1, curAttr);\n          }\n        } else {\n          this._activeBuffer.x = cols - 1;\n          if (chWidth === 2) {\n            // FIXME: check for xterm behavior\n            // What to do here? We got a wide char that does not fit into last cell\n            continue;\n          }\n        }\n      }\n\n      // insert combining char at last cursor position\n      // this._activeBuffer.x should never be 0 for a combining char\n      // since they always follow a cell consuming char\n      // therefore we can test for this._activeBuffer.x to avoid overflow left\n      if (shouldJoin && this._activeBuffer.x) {\n        const offset = bufferRow.getWidth(this._activeBuffer.x - 1) ? 1 : 2;\n        // if empty cell after fullwidth, need to go 2 cells back\n        // it is save to step 2 cells back here\n        // since an empty cell is only set by fullwidth chars\n        bufferRow.addCodepointToCell(this._activeBuffer.x - offset,\n          code, chWidth);\n        for (let delta = chWidth - oldWidth; --delta >= 0;) {\n          bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);\n        }\n        continue;\n      }\n\n      // insert mode: move characters to right\n      if (insertMode) {\n        // right shift cells according to the width\n        bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr));\n        // test last cell - since the last cell has only room for\n        // a halfwidth char any fullwidth shifted there is lost\n        // and will be set to empty cell\n        if (bufferRow.getWidth(cols - 1) === 2) {\n          bufferRow.setCellFromCodepoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr);\n        }\n      }\n\n      // write current char to buffer and advance cursor\n      bufferRow.setCellFromCodepoint(this._activeBuffer.x++, code, chWidth, curAttr);\n\n      // fullwidth char - also set next cell to placeholder stub and advance cursor\n      // for graphemes bigger than fullwidth we can simply loop to zero\n      // we already made sure above, that this._activeBuffer.x + chWidth will not overflow right\n      if (chWidth > 0) {\n        while (--chWidth) {\n          // other than a regular empty cell a cell following a wide char has no width\n          bufferRow.setCellFromCodepoint(this._activeBuffer.x++, 0, 0, curAttr);\n        }\n      }\n    }\n\n    this._parser.precedingJoinState = precedingJoinState;\n\n    // handle wide chars: reset cell to the right if it is second cell of a wide char\n    if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {\n      bufferRow.setCellFromCodepoint(this._activeBuffer.x, 0, 1, curAttr);\n    }\n\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n  }\n\n  /**\n   * Forward registerCsiHandler from parser.\n   */\n  public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable {\n    if (id.final === 't' && !id.prefix && !id.intermediates) {\n      // security: always check whether window option is allowed\n      return this._parser.registerCsiHandler(id, params => {\n        if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) {\n          return true;\n        }\n        return callback(params);\n      });\n    }\n    return this._parser.registerCsiHandler(id, callback);\n  }\n\n  /**\n   * Forward registerDcsHandler from parser.\n   */\n  public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable {\n    return this._parser.registerDcsHandler(id, new DcsHandler(callback));\n  }\n\n  /**\n   * Forward registerEscHandler from parser.\n   */\n  public registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable {\n    return this._parser.registerEscHandler(id, callback);\n  }\n\n  /**\n   * Forward registerOscHandler from parser.\n   */\n  public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this._parser.registerOscHandler(ident, new OscHandler(callback));\n  }\n\n  /**\n   * Forward registerApcHandler from parser.\n   */\n  public registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this._parser.registerApcHandler(ident, new ApcHandler(callback));\n  }\n\n  /**\n   * BEL\n   * Bell (Ctrl-G).\n   *\n   * @vt: #Y   C0    BEL   \"Bell\"  \"\\a, \\x07\"  \"Ring the bell.\"\n   * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle`\n   * and `ITerminalOptions.bellSound`.\n   */\n  public bell(): boolean {\n    this._onRequestBell.fire();\n    return true;\n  }\n\n  /**\n   * LF\n   * Line Feed or New Line (NL).  (LF  is Ctrl-J).\n   *\n   * @vt: #Y   C0    LF   \"Line Feed\"            \"\\n, \\x0A\"  \"Move the cursor one row down, scrolling if needed.\"\n   * Scrolling is restricted to scroll margins and will only happen on the bottom line.\n   *\n   * @vt: #Y   C0    VT   \"Vertical Tabulation\"  \"\\v, \\x0B\"  \"Treated as LF.\"\n   * @vt: #Y   C0    FF   \"Form Feed\"            \"\\f, \\x0C\"  \"Treated as LF.\"\n   */\n  public lineFeed(): boolean {\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n    if (this._optionsService.rawOptions.convertEol) {\n      this._activeBuffer.x = 0;\n    }\n    this._activeBuffer.y++;\n    if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {\n      this._activeBuffer.y--;\n      this._bufferService.scroll(this._eraseAttrData());\n    } else if (this._activeBuffer.y >= this._bufferService.rows) {\n      this._activeBuffer.y = this._bufferService.rows - 1;\n    } else {\n      // There was an explicit line feed (not just a carriage return), so clear the wrapped state of\n      // the line. This is particularly important on conpty/Windows where revisiting lines to\n      // reprint is common, especially on resize. Note that the windowsMode wrapped line heuristics\n      // can mess with this so windowsMode should be disabled, which is recommended on Windows build\n      // 21376 and above.\n      this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;\n    }\n    // If the end of the line is hit, prevent this action from wrapping around to the next line.\n    if (this._activeBuffer.x >= this._bufferService.cols) {\n      this._activeBuffer.x--;\n    }\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n\n    this._onLineFeed.fire();\n    return true;\n  }\n\n  /**\n   * CR\n   * Carriage Return (Ctrl-M).\n   *\n   * @vt: #Y   C0    CR   \"Carriage Return\"  \"\\r, \\x0D\"  \"Move the cursor to the beginning of the row.\"\n   */\n  public carriageReturn(): boolean {\n    this._activeBuffer.x = 0;\n    return true;\n  }\n\n  /**\n   * BS\n   * Backspace (Ctrl-H).\n   *\n   * @vt: #Y   C0    BS   \"Backspace\"  \"\\b, \\x08\"  \"Move the cursor one position to the left.\"\n   * By default it is not possible to move the cursor past the leftmost position.\n   * If `reverse wrap-around` (`CSI ? 45 h`) is set, a previous soft line wrap (DECAWM)\n   * can be undone with BS within the scroll margins. In that case the cursor will wrap back\n   * to the end of the previous row. Note that it is not possible to peek back into the scrollbuffer\n   * with the cursor, thus at the home position (top-leftmost cell) this has no effect.\n   */\n  public backspace(): boolean {\n    // reverse wrap-around is disabled\n    if (!this._coreService.decPrivateModes.reverseWraparound) {\n      this._restrictCursor();\n      if (this._activeBuffer.x > 0) {\n        this._activeBuffer.x--;\n      }\n      return true;\n    }\n\n    // reverse wrap-around is enabled\n    // other than for normal operation mode, reverse wrap-around allows the cursor\n    // to be at x=cols to be able to address the last cell of a row by BS\n    this._restrictCursor(this._bufferService.cols);\n\n    if (this._activeBuffer.x > 0) {\n      this._activeBuffer.x--;\n    } else {\n      /**\n       * reverse wrap-around handling:\n       * Our implementation deviates from xterm on purpose. Details:\n       * - only previous soft NLs can be reversed (isWrapped=true)\n       * - only works within scrollborders (top/bottom, left/right not yet supported)\n       * - cannot peek into scrollbuffer\n       * - any cursor movement sequence keeps working as expected\n       */\n      if (this._activeBuffer.x === 0\n        && this._activeBuffer.y > this._activeBuffer.scrollTop\n        && this._activeBuffer.y <= this._activeBuffer.scrollBottom\n        && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) {\n        this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;\n        this._activeBuffer.y--;\n        this._activeBuffer.x = this._bufferService.cols - 1;\n        // find last taken cell - last cell can have 3 different states:\n        // - hasContent(true) + hasWidth(1): narrow char - we are done\n        // - hasWidth(0): second part of wide char - we are done\n        // - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one\n        //   cell further back\n        const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;\n        if (line.hasWidth(this._activeBuffer.x) && !line.hasContent(this._activeBuffer.x)) {\n          this._activeBuffer.x--;\n          // We do this only once, since width=1 + hasContent=false currently happens only once\n          // before early wrapping of a wide char.\n          // This needs to be fixed once we support graphemes taking more than 2 cells.\n        }\n      }\n    }\n    this._restrictCursor();\n    return true;\n  }\n\n  /**\n   * TAB\n   * Horizontal Tab (HT) (Ctrl-I).\n   *\n   * @vt: #Y   C0    HT   \"Horizontal Tabulation\"  \"\\t, \\x09\"  \"Move the cursor to the next character tab stop.\"\n   */\n  public tab(): boolean {\n    if (this._activeBuffer.x >= this._bufferService.cols) {\n      return true;\n    }\n    const originalX = this._activeBuffer.x;\n    this._activeBuffer.x = this._activeBuffer.nextStop();\n    if (this._optionsService.rawOptions.screenReaderMode) {\n      this._onA11yTab.fire(this._activeBuffer.x - originalX);\n    }\n    return true;\n  }\n\n  /**\n   * SO\n   * Shift Out (Ctrl-N) -> Switch to Alternate Character Set.  This invokes the\n   * G1 character set.\n   *\n   * @vt: #P[Only limited ISO-2022 charset support.]  C0    SO   \"Shift Out\"  \"\\x0E\"  \"Switch to an alternative character set.\"\n   */\n  public shiftOut(): boolean {\n    this._charsetService.setgLevel(1);\n    return true;\n  }\n\n  /**\n   * SI\n   * Shift In (Ctrl-O) -> Switch to Standard Character Set.  This invokes the G0\n   * character set (the default).\n   *\n   * @vt: #Y   C0    SI   \"Shift In\"   \"\\x0F\"  \"Return to regular character set after Shift Out.\"\n   */\n  public shiftIn(): boolean {\n    this._charsetService.setgLevel(0);\n    return true;\n  }\n\n  /**\n   * Restrict cursor to viewport size / scroll margin (origin mode).\n   */\n  private _restrictCursor(maxCol: number = this._bufferService.cols - 1): void {\n    this._activeBuffer.x = Math.min(maxCol, Math.max(0, this._activeBuffer.x));\n    this._activeBuffer.y = this._coreService.decPrivateModes.origin\n      ? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y))\n      : Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y));\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n  }\n\n  /**\n   * Set absolute cursor position.\n   */\n  private _setCursor(x: number, y: number): void {\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n    if (this._coreService.decPrivateModes.origin) {\n      this._activeBuffer.x = x;\n      this._activeBuffer.y = this._activeBuffer.scrollTop + y;\n    } else {\n      this._activeBuffer.x = x;\n      this._activeBuffer.y = y;\n    }\n    this._restrictCursor();\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n  }\n\n  /**\n   * Set relative cursor position.\n   */\n  private _moveCursor(x: number, y: number): void {\n    // for relative changes we have to make sure we are within 0 .. cols/rows - 1\n    // before calculating the new position\n    this._restrictCursor();\n    this._setCursor(this._activeBuffer.x + x, this._activeBuffer.y + y);\n  }\n\n  /**\n   * CSI Ps A\n   * Cursor Up Ps Times (default = 1) (CUU).\n   *\n   * @vt: #Y CSI CUU   \"Cursor Up\"   \"CSI Ps A\"  \"Move cursor `Ps` times up (default=1).\"\n   * If the cursor would pass the top scroll margin, it will stop there.\n   */\n  public cursorUp(params: IParams): boolean {\n    // stop at scrollTop\n    const diffToTop = this._activeBuffer.y - this._activeBuffer.scrollTop;\n    if (diffToTop >= 0) {\n      this._moveCursor(0, -Math.min(diffToTop, params.params[0] || 1));\n    } else {\n      this._moveCursor(0, -(params.params[0] || 1));\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps B\n   * Cursor Down Ps Times (default = 1) (CUD).\n   *\n   * @vt: #Y CSI CUD   \"Cursor Down\"   \"CSI Ps B\"  \"Move cursor `Ps` times down (default=1).\"\n   * If the cursor would pass the bottom scroll margin, it will stop there.\n   */\n  public cursorDown(params: IParams): boolean {\n    // stop at scrollBottom\n    const diffToBottom = this._activeBuffer.scrollBottom - this._activeBuffer.y;\n    if (diffToBottom >= 0) {\n      this._moveCursor(0, Math.min(diffToBottom, params.params[0] || 1));\n    } else {\n      this._moveCursor(0, params.params[0] || 1);\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps C\n   * Cursor Forward Ps Times (default = 1) (CUF).\n   *\n   * @vt: #Y CSI CUF   \"Cursor Forward\"    \"CSI Ps C\"  \"Move cursor `Ps` times forward (default=1).\"\n   */\n  public cursorForward(params: IParams): boolean {\n    this._moveCursor(params.params[0] || 1, 0);\n    return true;\n  }\n\n  /**\n   * CSI Ps D\n   * Cursor Backward Ps Times (default = 1) (CUB).\n   *\n   * @vt: #Y CSI CUB   \"Cursor Backward\"   \"CSI Ps D\"  \"Move cursor `Ps` times backward (default=1).\"\n   */\n  public cursorBackward(params: IParams): boolean {\n    this._moveCursor(-(params.params[0] || 1), 0);\n    return true;\n  }\n\n  /**\n   * CSI Ps E\n   * Cursor Next Line Ps Times (default = 1) (CNL).\n   * Other than cursorDown (CUD) also set the cursor to first column.\n   *\n   * @vt: #Y CSI CNL   \"Cursor Next Line\"  \"CSI Ps E\"  \"Move cursor `Ps` times down (default=1) and to the first column.\"\n   * Same as CUD, additionally places the cursor at the first column.\n   */\n  public cursorNextLine(params: IParams): boolean {\n    this.cursorDown(params);\n    this._activeBuffer.x = 0;\n    return true;\n  }\n\n  /**\n   * CSI Ps F\n   * Cursor Previous Line Ps Times (default = 1) (CPL).\n   * Other than cursorUp (CUU) also set the cursor to first column.\n   *\n   * @vt: #Y CSI CPL   \"Cursor Backward\"   \"CSI Ps F\"  \"Move cursor `Ps` times up (default=1) and to the first column.\"\n   * Same as CUU, additionally places the cursor at the first column.\n   */\n  public cursorPrecedingLine(params: IParams): boolean {\n    this.cursorUp(params);\n    this._activeBuffer.x = 0;\n    return true;\n  }\n\n  /**\n   * CSI Ps G\n   * Cursor Character Absolute  [column] (default = [row,1]) (CHA).\n   *\n   * @vt: #Y CSI CHA   \"Cursor Horizontal Absolute\" \"CSI Ps G\" \"Move cursor to `Ps`-th column of the active row (default=1).\"\n   */\n  public cursorCharAbsolute(params: IParams): boolean {\n    this._setCursor((params.params[0] || 1) - 1, this._activeBuffer.y);\n    return true;\n  }\n\n  /**\n   * CSI Ps ; Ps H\n   * Cursor Position [row;column] (default = [1,1]) (CUP).\n   *\n   * @vt: #Y CSI CUP   \"Cursor Position\"   \"CSI Ps ; Ps H\"  \"Set cursor to position [`Ps`, `Ps`] (default = [1, 1]).\"\n   * If ORIGIN mode is set, places the cursor to the absolute position within the scroll margins.\n   * If ORIGIN mode is not set, places the cursor to the absolute position within the viewport.\n   * Note that the coordinates are 1-based, thus the top left position starts at `1 ; 1`.\n   */\n  public cursorPosition(params: IParams): boolean {\n    this._setCursor(\n      // col\n      (params.length >= 2) ? (params.params[1] || 1) - 1 : 0,\n      // row\n      (params.params[0] || 1) - 1\n    );\n    return true;\n  }\n\n  /**\n   * CSI Pm `  Character Position Absolute\n   *   [column] (default = [row,1]) (HPA).\n   * Currently same functionality as CHA.\n   *\n   * @vt: #Y CSI HPA   \"Horizontal Position Absolute\"  \"CSI Ps ` \" \"Same as CHA.\"\n   */\n  public charPosAbsolute(params: IParams): boolean {\n    this._setCursor((params.params[0] || 1) - 1, this._activeBuffer.y);\n    return true;\n  }\n\n  /**\n   * CSI Pm a  Character Position Relative\n   *   [columns] (default = [row,col+1]) (HPR)\n   *\n   * @vt: #Y CSI HPR   \"Horizontal Position Relative\"  \"CSI Ps a\"  \"Same as CUF.\"\n   */\n  public hPositionRelative(params: IParams): boolean {\n    this._moveCursor(params.params[0] || 1, 0);\n    return true;\n  }\n\n  /**\n   * CSI Pm d  Vertical Position Absolute (VPA)\n   *   [row] (default = [1,column])\n   *\n   * @vt: #Y CSI VPA   \"Vertical Position Absolute\"    \"CSI Ps d\"  \"Move cursor to `Ps`-th row (default=1).\"\n   */\n  public linePosAbsolute(params: IParams): boolean {\n    this._setCursor(this._activeBuffer.x, (params.params[0] || 1) - 1);\n    return true;\n  }\n\n  /**\n   * CSI Pm e  Vertical Position Relative (VPR)\n   *   [rows] (default = [row+1,column])\n   * reuse CSI Ps B ?\n   *\n   * @vt: #Y CSI VPR   \"Vertical Position Relative\"    \"CSI Ps e\"  \"Move cursor `Ps` times down (default=1).\"\n   */\n  public vPositionRelative(params: IParams): boolean {\n    this._moveCursor(0, params.params[0] || 1);\n    return true;\n  }\n\n  /**\n   * CSI Ps ; Ps f\n   *   Horizontal and Vertical Position [row;column] (default =\n   *   [1,1]) (HVP).\n   *   Same as CUP.\n   *\n   * @vt: #Y CSI HVP   \"Horizontal and Vertical Position\" \"CSI Ps ; Ps f\"  \"Same as CUP.\"\n   */\n  public hVPosition(params: IParams): boolean {\n    this.cursorPosition(params);\n    return true;\n  }\n\n  /**\n   * CSI Ps g  Tab Clear (TBC).\n   *     Ps = 0  -> Clear Current Column (default).\n   *     Ps = 3  -> Clear All.\n   * Potentially:\n   *   Ps = 2  -> Clear Stops on Line.\n   *   http://vt100.net/annarbor/aaa-ug/section6.html\n   *\n   * @vt: #Y CSI TBC   \"Tab Clear\" \"CSI Ps g\"  \"Clear tab stops at current position (0) or all (3) (default=0).\"\n   * Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported.\n   */\n  public tabClear(params: IParams): boolean {\n    const param = params.params[0];\n    if (param === 0) {\n      delete this._activeBuffer.tabs[this._activeBuffer.x];\n    } else if (param === 3) {\n      this._activeBuffer.tabs = {};\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps I\n   *   Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n   *\n   * @vt: #Y CSI CHT   \"Cursor Horizontal Tabulation\" \"CSI Ps I\" \"Move cursor `Ps` times tabs forward (default=1).\"\n   */\n  public cursorForwardTab(params: IParams): boolean {\n    if (this._activeBuffer.x >= this._bufferService.cols) {\n      return true;\n    }\n    let param = params.params[0] || 1;\n    while (param--) {\n      this._activeBuffer.x = this._activeBuffer.nextStop();\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps Z  Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n   *\n   * @vt: #Y CSI CBT   \"Cursor Backward Tabulation\"  \"CSI Ps Z\"  \"Move cursor `Ps` tabs backward (default=1).\"\n   */\n  public cursorBackwardTab(params: IParams): boolean {\n    if (this._activeBuffer.x >= this._bufferService.cols) {\n      return true;\n    }\n    let param = params.params[0] || 1;\n\n    while (param--) {\n      this._activeBuffer.x = this._activeBuffer.prevStop();\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps \" q  Select Character Protection Attribute (DECSCA).\n   *\n   * @vt: #Y CSI DECSCA   \"Select Character Protection Attribute\"  \"CSI Ps \" q\"  \"Whether DECSED and DECSEL can erase (0=default, 2) or not (1).\"\n   */\n  public selectProtected(params: IParams): boolean {\n    const p = params.params[0];\n    if (p === 1) this._curAttrData.bg |= BgFlags.PROTECTED;\n    if (p === 2 || p === 0) this._curAttrData.bg &= ~BgFlags.PROTECTED;\n    return true;\n  }\n\n\n  /**\n   * Helper method to erase cells in a terminal row.\n   * The cell gets replaced with the eraseChar of the terminal.\n   * @param y The row index relative to the viewport.\n   * @param start The start x index of the range to be erased.\n   * @param end The end x index of the range to be erased (exclusive).\n   * @param clearWrap clear the isWrapped flag\n   * @param respectProtect Whether to respect the protection attribute (DECSCA).\n   */\n  private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false, respectProtect: boolean = false): void {\n    const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y);\n    if (!line) {\n      return;\n    }\n    line.replaceCells(\n      start,\n      end,\n      this._activeBuffer.getNullCell(this._eraseAttrData()),\n      respectProtect\n    );\n    if (clearWrap) {\n      line.isWrapped = false;\n    }\n  }\n\n  /**\n   * Helper method to reset cells in a terminal row. The cell gets replaced with the eraseChar of\n   * the terminal and the isWrapped property is set to false.\n   * @param y row index\n   */\n  private _resetBufferLine(y: number, respectProtect: boolean = false): void {\n    const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y);\n    if (line) {\n      line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()), respectProtect);\n      this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y);\n      line.isWrapped = false;\n    }\n  }\n\n  /**\n   * CSI Ps J  Erase in Display (ED).\n   *     Ps = 0  -> Erase Below (default).\n   *     Ps = 1  -> Erase Above.\n   *     Ps = 2  -> Erase All.\n   *     Ps = 3  -> Erase Saved Lines (xterm).\n   * CSI ? Ps J\n   *   Erase in Display (DECSED).\n   *     Ps = 0  -> Selective Erase Below (default).\n   *     Ps = 1  -> Selective Erase Above.\n   *     Ps = 2  -> Selective Erase All.\n   *\n   * @vt: #Y CSI ED  \"Erase In Display\"  \"CSI Ps J\"  \"Erase various parts of the viewport.\"\n   * Supported param values:\n   *\n   * | Ps | Effect                                                       |\n   * | -- | ------------------------------------------------------------ |\n   * | 0  | Erase from the cursor through the end of the viewport.       |\n   * | 1  | Erase from the beginning of the viewport through the cursor. |\n   * | 2  | Erase complete viewport.                                     |\n   * | 3  | Erase scrollback.                                            |\n   *\n   * @vt: #Y CSI DECSED   \"Selective Erase In Display\"  \"CSI ? Ps J\"  \"Same as ED with respecting protection flag.\"\n   */\n  public eraseInDisplay(params: IParams, respectProtect: boolean = false): boolean {\n    this._restrictCursor(this._bufferService.cols);\n    let j;\n    switch (params.params[0]) {\n      case 0:\n        j = this._activeBuffer.y;\n        this._dirtyRowTracker.markDirty(j);\n        this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect);\n        for (; j < this._bufferService.rows; j++) {\n          this._resetBufferLine(j, respectProtect);\n        }\n        this._dirtyRowTracker.markDirty(j);\n        break;\n      case 1:\n        j = this._activeBuffer.y;\n        this._dirtyRowTracker.markDirty(j);\n        // Deleted front part of line and everything before. This line will no longer be wrapped.\n        this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect);\n        if (this._activeBuffer.x + 1 >= this._bufferService.cols) {\n          // Deleted entire previous line. This next line can no longer be wrapped.\n          const nextLine = this._activeBuffer.lines.get(j + 1);\n          if (nextLine) {\n            nextLine.isWrapped = false;\n          }\n        }\n        while (j--) {\n          this._resetBufferLine(j, respectProtect);\n        }\n        this._dirtyRowTracker.markDirty(0);\n        break;\n      case 2:\n        if (this._optionsService.rawOptions.scrollOnEraseInDisplay) {\n          j = this._bufferService.rows;\n          this._dirtyRowTracker.markRangeDirty(0, j - 1);\n          while (j--) {\n            const currentLine = this._activeBuffer.lines.get(this._activeBuffer.ybase + j);\n            if (currentLine?.getTrimmedLength()) {\n              break;\n            }\n          }\n          for (; j >= 0; j--) {\n            this._bufferService.scroll(this._eraseAttrData());\n          }\n        }\n        else {\n          j = this._bufferService.rows;\n          this._dirtyRowTracker.markDirty(j - 1);\n          while (j--) {\n            this._resetBufferLine(j, respectProtect);\n          }\n          this._dirtyRowTracker.markDirty(0);\n        }\n        break;\n      case 3:\n        // Clear scrollback (everything not in viewport)\n        const scrollBackSize = this._activeBuffer.lines.length - this._bufferService.rows;\n        if (scrollBackSize > 0) {\n          this._activeBuffer.lines.trimStart(scrollBackSize);\n          this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0);\n          this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0);\n          // Force a scroll event to refresh viewport\n          this._onScroll.fire(0);\n        }\n        break;\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps K  Erase in Line (EL).\n   *     Ps = 0  -> Erase to Right (default).\n   *     Ps = 1  -> Erase to Left.\n   *     Ps = 2  -> Erase All.\n   * CSI ? Ps K\n   *   Erase in Line (DECSEL).\n   *     Ps = 0  -> Selective Erase to Right (default).\n   *     Ps = 1  -> Selective Erase to Left.\n   *     Ps = 2  -> Selective Erase All.\n   *\n   * @vt: #Y CSI EL    \"Erase In Line\"  \"CSI Ps K\"  \"Erase various parts of the active row.\"\n   * Supported param values:\n   *\n   * | Ps | Effect                                                   |\n   * | -- | -------------------------------------------------------- |\n   * | 0  | Erase from the cursor through the end of the row.        |\n   * | 1  | Erase from the beginning of the line through the cursor. |\n   * | 2  | Erase complete line.                                     |\n   *\n   * @vt: #Y CSI DECSEL   \"Selective Erase In Line\"  \"CSI ? Ps K\"  \"Same as EL with respecting protecting flag.\"\n   */\n  public eraseInLine(params: IParams, respectProtect: boolean = false): boolean {\n    this._restrictCursor(this._bufferService.cols);\n    switch (params.params[0]) {\n      case 0:\n        this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect);\n        break;\n      case 1:\n        this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false, respectProtect);\n        break;\n      case 2:\n        this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect);\n        break;\n    }\n    this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n    return true;\n  }\n\n  /**\n   * CSI Ps L\n   * Insert Ps Line(s) (default = 1) (IL).\n   *\n   * @vt: #Y CSI IL  \"Insert Line\"   \"CSI Ps L\"  \"Insert `Ps` blank lines at active row (default=1).\"\n   * For every inserted line at the scroll top one line at the scroll bottom gets removed.\n   * The cursor is set to the first column.\n   * IL has no effect if the cursor is outside the scroll margins.\n   */\n  public insertLines(params: IParams): boolean {\n    this._restrictCursor();\n    let param = params.params[0] || 1;\n\n    if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {\n      return true;\n    }\n\n    const row: number = this._activeBuffer.ybase + this._activeBuffer.y;\n\n    const scrollBottomRowsOffset = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;\n    const scrollBottomAbsolute = this._bufferService.rows - 1 + this._activeBuffer.ybase - scrollBottomRowsOffset + 1;\n    while (param--) {\n      // test: echo -e '\\e[44m\\e[1L\\e[0m'\n      // blankLine(true) - xterm/linux behavior\n      this._activeBuffer.lines.splice(scrollBottomAbsolute - 1, 1);\n      this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));\n    }\n\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);\n    this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?\n    return true;\n  }\n\n  /**\n   * CSI Ps M\n   * Delete Ps Line(s) (default = 1) (DL).\n   *\n   * @vt: #Y CSI DL  \"Delete Line\"   \"CSI Ps M\"  \"Delete `Ps` lines at active row (default=1).\"\n   * For every deleted line at the scroll top one blank line at the scroll bottom gets appended.\n   * The cursor is set to the first column.\n   * DL has no effect if the cursor is outside the scroll margins.\n   */\n  public deleteLines(params: IParams): boolean {\n    this._restrictCursor();\n    let param = params.params[0] || 1;\n\n    if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {\n      return true;\n    }\n\n    const row: number = this._activeBuffer.ybase + this._activeBuffer.y;\n\n    let j: number;\n    j = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;\n    j = this._bufferService.rows - 1 + this._activeBuffer.ybase - j;\n    while (param--) {\n      // test: echo -e '\\e[44m\\e[1M\\e[0m'\n      // blankLine(true) - xterm/linux behavior\n      this._activeBuffer.lines.splice(row, 1);\n      this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));\n    }\n\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);\n    this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?\n    return true;\n  }\n\n  /**\n   * CSI Ps @\n   * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n   *\n   * @vt: #Y CSI ICH  \"Insert Characters\"   \"CSI Ps @\"  \"Insert `Ps` (blank) characters (default = 1).\"\n   * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the\n   * blank characters. Text between the cursor and right margin moves to the right. Characters moved\n   * past the right margin are lost.\n   *\n   *\n   * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual)\n   */\n  public insertChars(params: IParams): boolean {\n    this._restrictCursor();\n    const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);\n    if (line) {\n      line.insertCells(\n        this._activeBuffer.x,\n        params.params[0] || 1,\n        this._activeBuffer.getNullCell(this._eraseAttrData())\n      );\n      this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps P\n   * Delete Ps Character(s) (default = 1) (DCH).\n   *\n   * @vt: #Y CSI DCH   \"Delete Character\"  \"CSI Ps P\"  \"Delete `Ps` characters (default=1).\"\n   * As characters are deleted, the remaining characters between the cursor and right margin move to\n   * the left. Character attributes move with the characters. The terminal adds blank characters at\n   * the right margin.\n   *\n   *\n   * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual)\n   */\n  public deleteChars(params: IParams): boolean {\n    this._restrictCursor();\n    const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);\n    if (line) {\n      line.deleteCells(\n        this._activeBuffer.x,\n        params.params[0] || 1,\n        this._activeBuffer.getNullCell(this._eraseAttrData())\n      );\n      this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps S  Scroll up Ps lines (default = 1) (SU).\n   *\n   * @vt: #Y CSI SU  \"Scroll Up\"   \"CSI Ps S\"  \"Scroll `Ps` lines up (default=1).\"\n   *\n   *\n   * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm)\n   */\n  public scrollUp(params: IParams): boolean {\n    let param = params.params[0] || 1;\n\n    while (param--) {\n      this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1);\n      this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));\n    }\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    return true;\n  }\n\n  /**\n   * CSI Ps T  Scroll down Ps lines (default = 1) (SD).\n   *\n   * @vt: #Y CSI SD  \"Scroll Down\"   \"CSI Ps T\"  \"Scroll `Ps` lines down (default=1).\"\n   */\n  public scrollDown(params: IParams): boolean {\n    let param = params.params[0] || 1;\n\n    while (param--) {\n      this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1);\n      this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA));\n    }\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    return true;\n  }\n\n  /**\n   * CSI Ps SP @  Scroll left Ps columns (default = 1) (SL) ECMA-48\n   *\n   * Notation: (Pn)\n   * Representation: CSI Pn 02/00 04/00\n   * Parameter default value: Pn = 1\n   * SL causes the data in the presentation component to be moved by n character positions\n   * if the line orientation is horizontal, or by n line positions if the line orientation\n   * is vertical, such that the data appear to move to the left; where n equals the value of Pn.\n   * The active presentation position is not affected by this control function.\n   *\n   * Supported:\n   *   - always left shift (no line orientation setting respected)\n   *\n   * @vt: #Y CSI SL  \"Scroll Left\" \"CSI Ps SP @\" \"Scroll viewport `Ps` times to the left.\"\n   * SL moves the content of all lines within the scroll margins `Ps` times to the left.\n   * SL has no effect outside of the scroll margins.\n   */\n  public scrollLeft(params: IParams): boolean {\n    if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {\n      return true;\n    }\n    const param = params.params[0] || 1;\n    for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {\n      const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;\n      line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));\n      line.isWrapped = false;\n    }\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    return true;\n  }\n\n  /**\n   * CSI Ps SP A  Scroll right Ps columns (default = 1) (SR) ECMA-48\n   *\n   * Notation: (Pn)\n   * Representation: CSI Pn 02/00 04/01\n   * Parameter default value: Pn = 1\n   * SR causes the data in the presentation component to be moved by n character positions\n   * if the line orientation is horizontal, or by n line positions if the line orientation\n   * is vertical, such that the data appear to move to the right; where n equals the value of Pn.\n   * The active presentation position is not affected by this control function.\n   *\n   * Supported:\n   *   - always right shift (no line orientation setting respected)\n   *\n   * @vt: #Y CSI SR  \"Scroll Right\"  \"CSI Ps SP A\"   \"Scroll viewport `Ps` times to the right.\"\n   * SL moves the content of all lines within the scroll margins `Ps` times to the right.\n   * Content at the right margin is lost.\n   * SL has no effect outside of the scroll margins.\n   */\n  public scrollRight(params: IParams): boolean {\n    if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {\n      return true;\n    }\n    const param = params.params[0] || 1;\n    for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {\n      const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;\n      line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));\n      line.isWrapped = false;\n    }\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    return true;\n  }\n\n  /**\n   * CSI Pm ' }\n   * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.\n   *\n   * @vt: #Y CSI DECIC \"Insert Columns\"  \"CSI Ps ' }\"  \"Insert `Ps` columns at cursor position.\"\n   * DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll\n   * margins, moving content to the right. Content at the right margin is lost. DECIC has no effect\n   * outside the scrolling margins.\n   */\n  public insertColumns(params: IParams): boolean {\n    if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {\n      return true;\n    }\n    const param = params.params[0] || 1;\n    for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {\n      const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;\n      line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));\n      line.isWrapped = false;\n    }\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    return true;\n  }\n\n  /**\n   * CSI Pm ' ~\n   * Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.\n   *\n   * @vt: #Y CSI DECDC \"Delete Columns\"  \"CSI Ps ' ~\"  \"Delete `Ps` columns at cursor position.\"\n   * DECDC deletes `Ps` times columns at the cursor position for all lines with the scroll margins,\n   * moving content to the left. Blank columns are added at the right margin.\n   * DECDC has no effect outside the scrolling margins.\n   */\n  public deleteColumns(params: IParams): boolean {\n    if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {\n      return true;\n    }\n    const param = params.params[0] || 1;\n    for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {\n      const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;\n      line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));\n      line.isWrapped = false;\n    }\n    this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    return true;\n  }\n\n  /**\n   * CSI Ps X\n   * Erase Ps Character(s) (default = 1) (ECH).\n   *\n   * @vt: #Y CSI ECH   \"Erase Character\"   \"CSI Ps X\"  \"Erase `Ps` characters from current cursor position to the right (default=1).\"\n   * ED erases `Ps` characters from current cursor position to the right.\n   * ED works inside or outside the scrolling margins.\n   */\n  public eraseChars(params: IParams): boolean {\n    this._restrictCursor();\n    const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);\n    if (line) {\n      line.replaceCells(\n        this._activeBuffer.x,\n        this._activeBuffer.x + (params.params[0] || 1),\n        this._activeBuffer.getNullCell(this._eraseAttrData())\n      );\n      this._dirtyRowTracker.markDirty(this._activeBuffer.y);\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps b  Repeat the preceding graphic character Ps times (REP).\n   * From ECMA 48 (@see http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf)\n   *    Notation: (Pn)\n   *    Representation: CSI Pn 06/02\n   *    Parameter default value: Pn = 1\n   *    REP is used to indicate that the preceding character in the data stream,\n   *    if it is a graphic character (represented by one or more bit combinations) including SPACE,\n   *    is to be repeated n times, where n equals the value of Pn.\n   *    If the character preceding REP is a control function or part of a control function,\n   *    the effect of REP is not defined by this Standard.\n   *\n   * We extend xterm's behavior to allow repeating entire grapheme clusters.\n   * This isn't 100% xterm-compatible, but it seems saner and more useful.\n   *    - text attrs are applied normally\n   *    - wrap around is respected\n   *    - any valid sequence resets the carried forward char\n   *\n   * Note: To get reset on a valid sequence working correctly without much runtime penalty, the\n   * preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`.\n   *\n   * @vt: #Y CSI REP   \"Repeat Preceding Character\"    \"CSI Ps b\"  \"Repeat preceding character `Ps` times (default=1).\"\n   * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is\n   * set. REP has no effect if the sequence does not follow a printable ASCII character\n   * (NOOP for any other sequence in between or NON ASCII characters).\n   */\n  public repeatPrecedingCharacter(params: IParams): boolean {\n    const joinState = this._parser.precedingJoinState;\n    if (!joinState) {\n      return true;\n    }\n    // call print to insert the chars and handle correct wrapping\n    const length = params.params[0] || 1;\n    const chWidth = UnicodeService.extractWidth(joinState);\n    const x = this._activeBuffer.x - chWidth;\n    const bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;\n    const text = bufferRow.getString(x);\n    const data = new Uint32Array(text.length * length);\n    let idata = 0;\n    for (let itext = 0; itext < text.length;) {\n      const ch = text.codePointAt(itext) || 0;\n      data[idata++] = ch;\n      itext += ch > 0xffff ? 2 : 1;\n    }\n    let tlength = idata;\n    for (let i = 1; i < length; ++i) {\n      data.copyWithin(tlength, 0, idata);\n      tlength += idata;\n    }\n    this.print(data, 0, tlength);\n    return true;\n  }\n\n  /**\n   * CSI Ps c  Send Device Attributes (Primary DA).\n   *     Ps = 0  or omitted -> request attributes from terminal.  The\n   *     response depends on the decTerminalID resource setting.\n   *     -> CSI ? 1 ; 2 c  (``VT100 with Advanced Video Option'')\n   *     -> CSI ? 1 ; 0 c  (``VT101 with No Options'')\n   *     -> CSI ? 6 c  (``VT102'')\n   *     -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c  (``VT220'')\n   *   The VT100-style response parameters do not mean anything by\n   *   themselves.  VT220 parameters do, telling the host what fea-\n   *   tures the terminal supports:\n   *     Ps = 1  -> 132-columns.\n   *     Ps = 2  -> Printer.\n   *     Ps = 6  -> Selective erase.\n   *     Ps = 8  -> User-defined keys.\n   *     Ps = 9  -> National replacement character sets.\n   *     Ps = 1 5  -> Technical characters.\n   *     Ps = 2 2  -> ANSI color, e.g., VT525.\n   *     Ps = 2 9  -> ANSI text locator (i.e., DEC Locator mode).\n   *\n   * @vt: #Y CSI DA1   \"Primary Device Attributes\"     \"CSI c\"  \"Send primary device attributes.\"\n   *\n   *\n   * TODO: fix and cleanup response\n   */\n  public sendDeviceAttributesPrimary(params: IParams): boolean {\n    if (params.params[0] > 0) {\n      return true;\n    }\n    if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) {\n      this._coreService.triggerDataEvent(C0.ESC + '[?1;2c');\n    } else if (this._is('linux')) {\n      this._coreService.triggerDataEvent(C0.ESC + '[?6c');\n    }\n    return true;\n  }\n\n  /**\n   * CSI > Ps c\n   *   Send Device Attributes (Secondary DA).\n   *     Ps = 0  or omitted -> request the terminal's identification\n   *     code.  The response depends on the decTerminalID resource set-\n   *     ting.  It should apply only to VT220 and up, but xterm extends\n   *     this to VT100.\n   *     -> CSI  > Pp ; Pv ; Pc c\n   *   where Pp denotes the terminal type\n   *     Pp = 0  -> ``VT100''.\n   *     Pp = 1  -> ``VT220''.\n   *   and Pv is the firmware version (for xterm, this was originally\n   *   the XFree86 patch number, starting with 95).  In a DEC termi-\n   *   nal, Pc indicates the ROM cartridge registration number and is\n   *   always zero.\n   * More information:\n   *   xterm/charproc.c - line 2012, for more information.\n   *   vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n   *\n   * @vt: #Y CSI DA2   \"Secondary Device Attributes\"   \"CSI > c\" \"Send primary device attributes.\"\n   *\n   *\n   * TODO: fix and cleanup response\n   */\n  public sendDeviceAttributesSecondary(params: IParams): boolean {\n    if (params.params[0] > 0) {\n      return true;\n    }\n    // xterm and urxvt\n    // seem to spit this\n    // out around ~370 times (?).\n    if (this._is('xterm')) {\n      this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c');\n    } else if (this._is('rxvt-unicode')) {\n      this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c');\n    } else if (this._is('linux')) {\n      // not supported by linux console.\n      // linux console echoes parameters.\n      this._coreService.triggerDataEvent(params.params[0] + 'c');\n    } else if (this._is('screen')) {\n      this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');\n    }\n    return true;\n  }\n\n  /**\n   * CSI > Ps q\n   *   Ps = 0  => Report xterm name and version (XTVERSION).\n   *\n   * The response is a DCS sequence identifying the version: DCS > | text ST\n   *\n   * @vt: #Y CSI XTVERSION \"Report Xterm Version\" \"CSI > q\" \"Report the terminal name and version.\"\n   */\n  public sendXtVersion(params: IParams): boolean {\n    if (params.params[0] > 0) {\n      return true;\n    }\n    this._coreService.triggerDataEvent(`${C0.ESC}P>|xterm.js(${XTERM_VERSION})${C0.ESC}\\\\`);\n    return true;\n  }\n\n  /**\n   * Evaluate if the current terminal is the given argument.\n   * @param term The terminal name to evaluate\n   */\n  private _is(term: string): boolean {\n    return (this._optionsService.rawOptions.termName + '').startsWith(term);\n  }\n\n  /**\n   * CSI Pm h  Set Mode (SM).\n   *     Ps = 2  -> Keyboard Action Mode (AM).\n   *     Ps = 4  -> Insert Mode (IRM).\n   *     Ps = 1 2  -> Send/receive (SRM).\n   *     Ps = 2 0  -> Automatic Newline (LNM).\n   *\n   * @vt: #P[Only IRM is supported.]    CSI SM    \"Set Mode\"  \"CSI Pm h\"  \"Set various terminal modes.\"\n   * Supported param values by SM:\n   *\n   * | Param | Action                                 | Support |\n   * | ----- | -------------------------------------- | ------- |\n   * | 2     | Keyboard Action Mode (KAM). Always on. | #N      |\n   * | 4     | Insert Mode (IRM).                     | #Y      |\n   * | 12    | Send/receive (SRM). Always off.        | #N      |\n   * | 20    | Automatic Newline (LNM).               | #Y      |\n   */\n  public setMode(params: IParams): boolean {\n    for (let i = 0; i < params.length; i++) {\n      switch (params.params[i]) {\n        case 4:\n          this._coreService.modes.insertMode = true;\n          break;\n        case 20:\n          this._optionsService.options.convertEol = true;\n          break;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * CSI ? Pm h\n   *   DEC Private Mode Set (DECSET).\n   *     Ps = 1  -> Application Cursor Keys (DECCKM).\n   *     Ps = 2  -> Designate USASCII for character sets G0-G3\n   *     (DECANM), and set VT100 mode.\n   *     Ps = 3  -> 132 Column Mode (DECCOLM).\n   *     Ps = 4  -> Smooth (Slow) Scroll (DECSCLM).\n   *     Ps = 5  -> Reverse Video (DECSCNM).\n   *     Ps = 6  -> Origin Mode (DECOM).\n   *     Ps = 7  -> Wraparound Mode (DECAWM).\n   *     Ps = 8  -> Auto-repeat Keys (DECARM).\n   *     Ps = 9  -> Send Mouse X & Y on button press.  See the sec-\n   *     tion Mouse Tracking.\n   *     Ps = 1 0  -> Show toolbar (rxvt).\n   *     Ps = 1 2  -> Start Blinking Cursor (att610).\n   *     Ps = 1 8  -> Print form feed (DECPFF).\n   *     Ps = 1 9  -> Set print extent to full screen (DECPEX).\n   *     Ps = 2 5  -> Show Cursor (DECTCEM).\n   *     Ps = 3 0  -> Show scrollbar (rxvt).\n   *     Ps = 3 5  -> Enable font-shifting functions (rxvt).\n   *     Ps = 3 8  -> Enter Tektronix Mode (DECTEK).\n   *     Ps = 4 0  -> Allow 80 -> 132 Mode.\n   *     Ps = 4 1  -> more(1) fix (see curses resource).\n   *     Ps = 4 2  -> Enable Nation Replacement Character sets (DECN-\n   *     RCM).\n   *     Ps = 4 4  -> Turn On Margin Bell.\n   *     Ps = 4 5  -> Reverse-wraparound Mode.\n   *     Ps = 4 6  -> Start Logging.  This is normally disabled by a\n   *     compile-time option.\n   *     Ps = 4 7  -> Use Alternate Screen Buffer.  (This may be dis-\n   *     abled by the titeInhibit resource).\n   *     Ps = 6 6  -> Application keypad (DECNKM).\n   *     Ps = 6 7  -> Backarrow key sends backspace (DECBKM).\n   *     Ps = 1 0 0 0  -> Send Mouse X & Y on button press and\n   *     release.  See the section Mouse Tracking.\n   *     Ps = 1 0 0 1  -> Use Hilite Mouse Tracking.\n   *     Ps = 1 0 0 2  -> Use Cell Motion Mouse Tracking.\n   *     Ps = 1 0 0 3  -> Use All Motion Mouse Tracking.\n   *     Ps = 1 0 0 4  -> Send FocusIn/FocusOut events.\n   *     Ps = 1 0 0 5  -> Enable Extended Mouse Mode.\n   *     Ps = 1 0 1 0  -> Scroll to bottom on tty output (rxvt).\n   *     Ps = 1 0 1 1  -> Scroll to bottom on key press (rxvt).\n   *     Ps = 1 0 3 4  -> Interpret \"meta\" key, sets eighth bit.\n   *     (enables the eightBitInput resource).\n   *     Ps = 1 0 3 5  -> Enable special modifiers for Alt and Num-\n   *     Lock keys.  (This enables the numLock resource).\n   *     Ps = 1 0 3 6  -> Send ESC   when Meta modifies a key.  (This\n   *     enables the metaSendsEscape resource).\n   *     Ps = 1 0 3 7  -> Send DEL from the editing-keypad Delete\n   *     key.\n   *     Ps = 1 0 3 9  -> Send ESC  when Alt modifies a key.  (This\n   *     enables the altSendsEscape resource).\n   *     Ps = 1 0 4 0  -> Keep selection even if not highlighted.\n   *     (This enables the keepSelection resource).\n   *     Ps = 1 0 4 1  -> Use the CLIPBOARD selection.  (This enables\n   *     the selectToClipboard resource).\n   *     Ps = 1 0 4 2  -> Enable Urgency window manager hint when\n   *     Control-G is received.  (This enables the bellIsUrgent\n   *     resource).\n   *     Ps = 1 0 4 3  -> Enable raising of the window when Control-G\n   *     is received.  (enables the popOnBell resource).\n   *     Ps = 1 0 4 7  -> Use Alternate Screen Buffer.  (This may be\n   *     disabled by the titeInhibit resource).\n   *     Ps = 1 0 4 8  -> Save cursor as in DECSC.  (This may be dis-\n   *     abled by the titeInhibit resource).\n   *     Ps = 1 0 4 9  -> Save cursor as in DECSC and use Alternate\n   *     Screen Buffer, clearing it first.  (This may be disabled by\n   *     the titeInhibit resource).  This combines the effects of the 1\n   *     0 4 7  and 1 0 4 8  modes.  Use this with terminfo-based\n   *     applications rather than the 4 7  mode.\n   *     Ps = 1 0 5 0  -> Set terminfo/termcap function-key mode.\n   *     Ps = 1 0 5 1  -> Set Sun function-key mode.\n   *     Ps = 1 0 5 2  -> Set HP function-key mode.\n   *     Ps = 1 0 5 3  -> Set SCO function-key mode.\n   *     Ps = 1 0 6 0  -> Set legacy keyboard emulation (X11R6).\n   *     Ps = 1 0 6 1  -> Set VT220 keyboard emulation.\n   *     Ps = 2 0 0 4  -> Set bracketed paste mode.\n   * Modes:\n   *   http: *vt100.net/docs/vt220-rm/chapter4.html\n   *\n   * @vt: #P[See below for supported modes.]    CSI DECSET  \"DEC Private Set Mode\" \"CSI ? Pm h\"  \"Set various terminal attributes.\"\n   * Supported param values by DECSET:\n   *\n   * | param | Action                                                  | Support |\n   * | ----- | ------------------------------------------------------- | --------|\n   * | 1     | Application Cursor Keys (DECCKM).                       | #Y      |\n   * | 2     | Designate US-ASCII for character sets G0-G3 (DECANM).   | #Y      |\n   * | 3     | 132 Column Mode (DECCOLM).                              | #Y      |\n   * | 6     | Origin Mode (DECOM).                                    | #Y      |\n   * | 7     | Auto-wrap Mode (DECAWM).                                | #Y      |\n   * | 8     | Auto-repeat Keys (DECARM). Always on.                   | #N      |\n   * | 9     | X10 xterm mouse protocol.                               | #Y      |\n   * | 12    | Start Blinking Cursor.                                  | #P[Requires the allowSetCursorBlink quirk option enabled.] |\n   * | 25    | Show Cursor (DECTCEM).                                  | #Y      |\n   * | 45    | Reverse wrap-around.                                    | #Y      |\n   * | 47    | Use Alternate Screen Buffer.                            | #Y      |\n   * | 66    | Application keypad (DECNKM).                            | #Y      |\n   * | 1000  | X11 xterm mouse protocol.                               | #Y      |\n   * | 1002  | Use Cell Motion Mouse Tracking.                         | #Y      |\n   * | 1003  | Use All Motion Mouse Tracking.                          | #Y      |\n   * | 1004  | Send FocusIn/FocusOut events                            | #Y      |\n   * | 1005  | Enable UTF-8 Mouse Mode.                                | #N      |\n   * | 1006  | Enable SGR Mouse Mode.                                  | #Y      |\n   * | 1015  | Enable urxvt Mouse Mode.                                | #N      |\n   * | 1016  | Enable SGR-Pixels Mouse Mode.                           | #Y      |\n   * | 1047  | Use Alternate Screen Buffer.                            | #Y      |\n   * | 1048  | Save cursor as in DECSC.                                | #Y      |\n   * | 1049  | Save cursor and switch to alternate buffer clearing it. | #P[Does not clear the alternate buffer.] |\n   * | 2004  | Set bracketed paste mode.                               | #Y      |\n   *\n   *\n   * FIXME: implement DECSCNM, 1049 should clear altbuffer\n   */\n  public setModePrivate(params: IParams): boolean {\n    for (let i = 0; i < params.length; i++) {\n      switch (params.params[i]) {\n        case 1:\n          this._coreService.decPrivateModes.applicationCursorKeys = true;\n          break;\n        case 2:\n          this._charsetService.setgCharset(0, DEFAULT_CHARSET);\n          this._charsetService.setgCharset(1, DEFAULT_CHARSET);\n          this._charsetService.setgCharset(2, DEFAULT_CHARSET);\n          this._charsetService.setgCharset(3, DEFAULT_CHARSET);\n          // set VT100 mode here\n          break;\n        case 3:\n          /**\n           * DECCOLM - 132 column mode.\n           * This is only active if 'SetWinLines' (24) is enabled\n           * through `options.windowsOptions`.\n           */\n          if (this._optionsService.rawOptions.windowOptions.setWinLines) {\n            this._bufferService.resize(132, this._bufferService.rows);\n            this._onRequestReset.fire();\n          }\n          break;\n        case 6:\n          this._coreService.decPrivateModes.origin = true;\n          this._setCursor(0, 0);\n          break;\n        case 7:\n          this._coreService.decPrivateModes.wraparound = true;\n          break;\n        case 12:\n          if (this._optionsService.rawOptions.quirks?.allowSetCursorBlink) {\n            this._optionsService.options.cursorBlink = true;\n          }\n          break;\n        case 45:\n          this._coreService.decPrivateModes.reverseWraparound = true;\n          break;\n        case 66:\n          this._logService.debug('Serial port requested application keypad.');\n          this._coreService.decPrivateModes.applicationKeypad = true;\n          this._onRequestSyncScrollBar.fire();\n          break;\n        case 9: // X10 Mouse\n          // no release, no motion, no wheel, no modifiers.\n          this._mouseStateService.activeProtocol = 'X10';\n          break;\n        case 1000: // vt200 mouse\n          // no motion.\n          this._mouseStateService.activeProtocol = 'VT200';\n          break;\n        case 1002: // button event mouse\n          this._mouseStateService.activeProtocol = 'DRAG';\n          break;\n        case 1003: // any event mouse\n          // any event - sends motion events,\n          // even if there is no button held down.\n          this._mouseStateService.activeProtocol = 'ANY';\n          break;\n        case 1004: // send focusin/focusout events\n          // focusin: ^[[I\n          // focusout: ^[[O\n          this._coreService.decPrivateModes.sendFocus = true;\n          this._onRequestSendFocus.fire();\n          break;\n        case 1005: // utf8 ext mode mouse - removed in #2507\n          this._logService.debug('DECSET 1005 not supported (see #2507)');\n          break;\n        case 1006: // sgr ext mode mouse\n          this._mouseStateService.activeEncoding = 'SGR';\n          break;\n        case 1015: // urxvt ext mode mouse - removed in #2507\n          this._logService.debug('DECSET 1015 not supported (see #2507)');\n          break;\n        case 1016: // sgr pixels mode mouse\n          this._mouseStateService.activeEncoding = 'SGR_PIXELS';\n          break;\n        case 25: // show cursor\n          this._coreService.isCursorHidden = false;\n          break;\n        case 1048: // alt screen cursor\n          this.saveCursor();\n          break;\n        case 1049: // alt screen buffer cursor\n          this.saveCursor();\n        // FALL-THROUGH\n        case 47: // alt screen buffer\n        case 1047: // alt screen buffer\n          // Swap kitty keyboard flags: save main, restore alt\n          if (this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {\n            const state = this._coreService.kittyKeyboard;\n            state.mainFlags = state.flags;\n            state.flags = state.altFlags;\n          }\n          this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());\n          this._coreService.isCursorInitialized = true;\n          this._onRequestRefreshRows.fire(undefined);\n          this._onRequestSyncScrollBar.fire();\n          break;\n        case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)\n          this._coreService.decPrivateModes.bracketedPasteMode = true;\n          break;\n        case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)\n          this._coreService.decPrivateModes.synchronizedOutput = true;\n          break;\n        case 2031: // color scheme updates (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)\n          if (this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery ?? true) {\n            this._coreService.decPrivateModes.colorSchemeUpdates = true;\n          }\n          break;\n        case 9001: // win32-input-mode (https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md)\n          if (this._optionsService.rawOptions.vtExtensions?.win32InputMode) {\n            this._coreService.decPrivateModes.win32InputMode = true;\n          }\n          break;\n      }\n    }\n    return true;\n  }\n\n\n  /**\n   * CSI Pm l  Reset Mode (RM).\n   *     Ps = 2  -> Keyboard Action Mode (AM).\n   *     Ps = 4  -> Replace Mode (IRM).\n   *     Ps = 1 2  -> Send/receive (SRM).\n   *     Ps = 2 0  -> Normal Linefeed (LNM).\n   *\n   * @vt: #P[Only IRM is supported.]    CSI RM    \"Reset Mode\"  \"CSI Pm l\"  \"Set various terminal attributes.\"\n   * Supported param values by RM:\n   *\n   * | Param | Action                                 | Support |\n   * | ----- | -------------------------------------- | ------- |\n   * | 2     | Keyboard Action Mode (KAM). Always on. | #N      |\n   * | 4     | Replace Mode (IRM). (default)          | #Y      |\n   * | 12    | Send/receive (SRM). Always off.        | #N      |\n   * | 20    | Normal Linefeed (LNM).                 | #Y      |\n   *\n   *\n   * FIXME: why is LNM commented out?\n   */\n  public resetMode(params: IParams): boolean {\n    for (let i = 0; i < params.length; i++) {\n      switch (params.params[i]) {\n        case 4:\n          this._coreService.modes.insertMode = false;\n          break;\n        case 20:\n          this._optionsService.options.convertEol = false;\n          break;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * CSI ? Pm l\n   *   DEC Private Mode Reset (DECRST).\n   *     Ps = 1  -> Normal Cursor Keys (DECCKM).\n   *     Ps = 2  -> Designate VT52 mode (DECANM).\n   *     Ps = 3  -> 80 Column Mode (DECCOLM).\n   *     Ps = 4  -> Jump (Fast) Scroll (DECSCLM).\n   *     Ps = 5  -> Normal Video (DECSCNM).\n   *     Ps = 6  -> Normal Cursor Mode (DECOM).\n   *     Ps = 7  -> No Wraparound Mode (DECAWM).\n   *     Ps = 8  -> No Auto-repeat Keys (DECARM).\n   *     Ps = 9  -> Don't send Mouse X & Y on button press.\n   *     Ps = 1 0  -> Hide toolbar (rxvt).\n   *     Ps = 1 2  -> Stop Blinking Cursor (att610).\n   *     Ps = 1 8  -> Don't print form feed (DECPFF).\n   *     Ps = 1 9  -> Limit print to scrolling region (DECPEX).\n   *     Ps = 2 5  -> Hide Cursor (DECTCEM).\n   *     Ps = 3 0  -> Don't show scrollbar (rxvt).\n   *     Ps = 3 5  -> Disable font-shifting functions (rxvt).\n   *     Ps = 4 0  -> Disallow 80 -> 132 Mode.\n   *     Ps = 4 1  -> No more(1) fix (see curses resource).\n   *     Ps = 4 2  -> Disable Nation Replacement Character sets (DEC-\n   *     NRCM).\n   *     Ps = 4 4  -> Turn Off Margin Bell.\n   *     Ps = 4 5  -> No Reverse-wraparound Mode.\n   *     Ps = 4 6  -> Stop Logging.  (This is normally disabled by a\n   *     compile-time option).\n   *     Ps = 4 7  -> Use Normal Screen Buffer.\n   *     Ps = 6 6  -> Numeric keypad (DECNKM).\n   *     Ps = 6 7  -> Backarrow key sends delete (DECBKM).\n   *     Ps = 1 0 0 0  -> Don't send Mouse X & Y on button press and\n   *     release.  See the section Mouse Tracking.\n   *     Ps = 1 0 0 1  -> Don't use Hilite Mouse Tracking.\n   *     Ps = 1 0 0 2  -> Don't use Cell Motion Mouse Tracking.\n   *     Ps = 1 0 0 3  -> Don't use All Motion Mouse Tracking.\n   *     Ps = 1 0 0 4  -> Don't send FocusIn/FocusOut events.\n   *     Ps = 1 0 0 5  -> Disable Extended Mouse Mode.\n   *     Ps = 1 0 1 0  -> Don't scroll to bottom on tty output\n   *     (rxvt).\n   *     Ps = 1 0 1 1  -> Don't scroll to bottom on key press (rxvt).\n   *     Ps = 1 0 3 4  -> Don't interpret \"meta\" key.  (This disables\n   *     the eightBitInput resource).\n   *     Ps = 1 0 3 5  -> Disable special modifiers for Alt and Num-\n   *     Lock keys.  (This disables the numLock resource).\n   *     Ps = 1 0 3 6  -> Don't send ESC  when Meta modifies a key.\n   *     (This disables the metaSendsEscape resource).\n   *     Ps = 1 0 3 7  -> Send VT220 Remove from the editing-keypad\n   *     Delete key.\n   *     Ps = 1 0 3 9  -> Don't send ESC  when Alt modifies a key.\n   *     (This disables the altSendsEscape resource).\n   *     Ps = 1 0 4 0  -> Do not keep selection when not highlighted.\n   *     (This disables the keepSelection resource).\n   *     Ps = 1 0 4 1  -> Use the PRIMARY selection.  (This disables\n   *     the selectToClipboard resource).\n   *     Ps = 1 0 4 2  -> Disable Urgency window manager hint when\n   *     Control-G is received.  (This disables the bellIsUrgent\n   *     resource).\n   *     Ps = 1 0 4 3  -> Disable raising of the window when Control-\n   *     G is received.  (This disables the popOnBell resource).\n   *     Ps = 1 0 4 7  -> Use Normal Screen Buffer, clearing screen\n   *     first if in the Alternate Screen.  (This may be disabled by\n   *     the titeInhibit resource).\n   *     Ps = 1 0 4 8  -> Restore cursor as in DECRC.  (This may be\n   *     disabled by the titeInhibit resource).\n   *     Ps = 1 0 4 9  -> Use Normal Screen Buffer and restore cursor\n   *     as in DECRC.  (This may be disabled by the titeInhibit\n   *     resource).  This combines the effects of the 1 0 4 7  and 1 0\n   *     4 8  modes.  Use this with terminfo-based applications rather\n   *     than the 4 7  mode.\n   *     Ps = 1 0 5 0  -> Reset terminfo/termcap function-key mode.\n   *     Ps = 1 0 5 1  -> Reset Sun function-key mode.\n   *     Ps = 1 0 5 2  -> Reset HP function-key mode.\n   *     Ps = 1 0 5 3  -> Reset SCO function-key mode.\n   *     Ps = 1 0 6 0  -> Reset legacy keyboard emulation (X11R6).\n   *     Ps = 1 0 6 1  -> Reset keyboard emulation to Sun/PC style.\n   *     Ps = 2 0 0 4  -> Reset bracketed paste mode.\n   *\n   * @vt: #P[See below for supported modes.]    CSI DECRST  \"DEC Private Reset Mode\" \"CSI ? Pm l\"  \"Reset various terminal attributes.\"\n   * Supported param values by DECRST:\n   *\n   * | param | Action                                                  | Support |\n   * | ----- | ------------------------------------------------------- | ------- |\n   * | 1     | Normal Cursor Keys (DECCKM).                            | #Y      |\n   * | 2     | Designate VT52 mode (DECANM).                           | #N      |\n   * | 3     | 80 Column Mode (DECCOLM).                               | #B[Switches to old column width instead of 80.] |\n   * | 6     | Normal Cursor Mode (DECOM).                             | #Y      |\n   * | 7     | No Wraparound Mode (DECAWM).                            | #Y      |\n   * | 8     | No Auto-repeat Keys (DECARM).                           | #N      |\n   * | 9     | Don't send Mouse X & Y on button press.                 | #Y      |\n   * | 12    | Stop Blinking Cursor.                                   | #P[Requires the allowSetCursorBlink quirk option enabled.] |\n   * | 25    | Hide Cursor (DECTCEM).                                  | #Y      |\n   * | 45    | No reverse wrap-around.                                 | #Y      |\n   * | 47    | Use Normal Screen Buffer.                               | #Y      |\n   * | 66    | Numeric keypad (DECNKM).                                | #Y      |\n   * | 1000  | Don't send Mouse reports.                               | #Y      |\n   * | 1002  | Don't use Cell Motion Mouse Tracking.                   | #Y      |\n   * | 1003  | Don't use All Motion Mouse Tracking.                    | #Y      |\n   * | 1004  | Don't send FocusIn/FocusOut events.                     | #Y      |\n   * | 1005  | Disable UTF-8 Mouse Mode.                               | #N      |\n   * | 1006  | Disable SGR Mouse Mode.                                 | #Y      |\n   * | 1015  | Disable urxvt Mouse Mode.                               | #N      |\n   * | 1016  | Disable SGR-Pixels Mouse Mode.                          | #Y      |\n   * | 1047  | Use Normal Screen Buffer (clearing screen if in alt).   | #Y      |\n   * | 1048  | Restore cursor as in DECRC.                             | #Y      |\n   * | 1049  | Use Normal Screen Buffer and restore cursor.            | #Y      |\n   * | 2004  | Reset bracketed paste mode.                             | #Y      |\n   *\n   *\n   * FIXME: DECCOLM is currently broken (already fixed in window options PR)\n   */\n  public resetModePrivate(params: IParams): boolean {\n    for (let i = 0; i < params.length; i++) {\n      switch (params.params[i]) {\n        case 1:\n          this._coreService.decPrivateModes.applicationCursorKeys = false;\n          break;\n        case 3:\n          /**\n           * DECCOLM - 80 column mode.\n           * This is only active if 'SetWinLines' (24) is enabled\n           * through `options.windowsOptions`.\n           */\n          if (this._optionsService.rawOptions.windowOptions.setWinLines) {\n            this._bufferService.resize(80, this._bufferService.rows);\n            this._onRequestReset.fire();\n          }\n          break;\n        case 6:\n          this._coreService.decPrivateModes.origin = false;\n          this._setCursor(0, 0);\n          break;\n        case 7:\n          this._coreService.decPrivateModes.wraparound = false;\n          break;\n        case 12:\n          if (this._optionsService.rawOptions.quirks?.allowSetCursorBlink) {\n            this._optionsService.options.cursorBlink = false;\n          }\n          break;\n        case 45:\n          this._coreService.decPrivateModes.reverseWraparound = false;\n          break;\n        case 66:\n          this._logService.debug('Switching back to normal keypad.');\n          this._coreService.decPrivateModes.applicationKeypad = false;\n          this._onRequestSyncScrollBar.fire();\n          break;\n        case 9: // X10 Mouse\n        case 1000: // vt200 mouse\n        case 1002: // button event mouse\n        case 1003: // any event mouse\n          this._mouseStateService.activeProtocol = 'NONE';\n          break;\n        case 1004: // send focusin/focusout events\n          this._coreService.decPrivateModes.sendFocus = false;\n          break;\n        case 1005: // utf8 ext mode mouse - removed in #2507\n          this._logService.debug('DECRST 1005 not supported (see #2507)');\n          break;\n        case 1006: // sgr ext mode mouse\n          this._mouseStateService.activeEncoding = 'DEFAULT';\n          break;\n        case 1015: // urxvt ext mode mouse - removed in #2507\n          this._logService.debug('DECRST 1015 not supported (see #2507)');\n          break;\n        case 1016: // sgr pixels mode mouse\n          this._mouseStateService.activeEncoding = 'DEFAULT';\n          break;\n        case 25: // hide cursor\n          this._coreService.isCursorHidden = true;\n          break;\n        case 1048: // alt screen cursor\n          this.restoreCursor();\n          break;\n        case 1049: // alt screen buffer cursor\n        // FALL-THROUGH\n        case 47: // normal screen buffer\n        case 1047: // normal screen buffer - clearing it first\n          // Swap kitty keyboard flags: save alt, restore main\n          if (this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {\n            const state = this._coreService.kittyKeyboard;\n            state.altFlags = state.flags;\n            state.flags = state.mainFlags;\n          }\n          // Ensure the selection manager has the correct buffer\n          this._bufferService.buffers.activateNormalBuffer();\n          if (params.params[i] === 1049) {\n            this.restoreCursor();\n          }\n          this._coreService.isCursorInitialized = true;\n          this._onRequestRefreshRows.fire(undefined);\n          this._onRequestSyncScrollBar.fire();\n          break;\n        case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)\n          this._coreService.decPrivateModes.bracketedPasteMode = false;\n          break;\n        case 2026: // synchronized output (https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md)\n          this._coreService.decPrivateModes.synchronizedOutput = false;\n          this._onRequestRefreshRows.fire(undefined);\n          break;\n        case 2031: // color scheme updates (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)\n          if (this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery ?? true) {\n            this._coreService.decPrivateModes.colorSchemeUpdates = false;\n          }\n          break;\n        case 9001: // win32-input-mode\n          if (this._optionsService.rawOptions.vtExtensions?.win32InputMode) {\n            this._coreService.decPrivateModes.win32InputMode = false;\n          }\n          break;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps $ p Request ANSI Mode (DECRQM).\n   *\n   * Reports CSI Ps; Pm $ y (DECRPM), where Ps is the mode number as in SM/RM,\n   * and Pm is the mode value:\n   *    0 - not recognized\n   *    1 - set\n   *    2 - reset\n   *    3 - permanently set\n   *    4 - permanently reset\n   *\n   * @vt: #Y  CSI   DECRQM  \"Request Mode\"  \"CSI Ps $p\"  \"Request mode state.\"\n   * Returns a report as `CSI Ps; Pm $ y` (DECRPM), where `Ps` is the mode number as in SM/RM\n   * or DECSET/DECRST, and `Pm` is the mode value:\n   * - 0: not recognized\n   * - 1: set\n   * - 2: reset\n   * - 3: permanently set\n   * - 4: permanently reset\n   *\n   * For modes not understood xterm.js always returns `notRecognized`. In general this means,\n   * that a certain operation mode is not implemented and cannot be used.\n   *\n   * Modes changing the active terminal buffer (47, 1047, 1049) are not subqueried\n   * and only report, whether the alternate buffer is set.\n   *\n   * Mouse encodings and mouse protocols are handled mutual exclusive,\n   * thus only one of each of those can be set at a given time.\n   *\n   * There is a chance, that some mode reports are not fully in line with xterm.js' behavior,\n   * e.g. if the default implementation already exposes a certain behavior. If you find\n   * discrepancies in the mode reports, please file a bug.\n   */\n  public requestMode(params: IParams, ansi: boolean): boolean {\n    // return value as in DECRPM\n    const enum V {\n      NOT_RECOGNIZED = 0,\n      SET = 1,\n      RESET = 2,\n      PERMANENTLY_SET = 3,\n      PERMANENTLY_RESET = 4\n    }\n\n    // access helpers\n    const dm = this._coreService.decPrivateModes;\n    const { activeProtocol: mouseProtocol, activeEncoding: mouseEncoding } = this._mouseStateService;\n    const cs = this._coreService;\n    const { buffers, cols } = this._bufferService;\n    const { active, alt } = buffers;\n    const opts = this._optionsService.rawOptions;\n\n    const f = (m: number, v: V): boolean => {\n      cs.triggerDataEvent(`${C0.ESC}[${ansi ? '' : '?'}${m};${v}$y`);\n      return true;\n    };\n    const b2v = (value: boolean): V => value ? V.SET : V.RESET;\n\n    const p = params.params[0];\n\n    if (ansi) {\n      if (p === 2) return f(p, V.PERMANENTLY_RESET);\n      if (p === 4) return f(p, b2v(cs.modes.insertMode));\n      if (p === 12) return f(p, V.PERMANENTLY_SET);\n      if (p === 20) return f(p, b2v(opts.convertEol));\n      return f(p, V.NOT_RECOGNIZED);\n    }\n\n    if (p === 1) return f(p, b2v(dm.applicationCursorKeys));\n    if (p === 3) return f(p, opts.windowOptions.setWinLines ? (cols === 80 ? V.RESET : cols === 132 ? V.SET : V.NOT_RECOGNIZED) : V.NOT_RECOGNIZED);\n    if (p === 6) return f(p, b2v(dm.origin));\n    if (p === 7) return f(p, b2v(dm.wraparound));\n    if (p === 8) return f(p, V.PERMANENTLY_SET);\n    if (p === 9) return f(p, b2v(mouseProtocol === 'X10'));\n    if (p === 12) return f(p, b2v(opts.cursorBlink));\n    if (p === 25) return f(p, b2v(!cs.isCursorHidden));\n    if (p === 45) return f(p, b2v(dm.reverseWraparound));\n    if (p === 66) return f(p, b2v(dm.applicationKeypad));\n    if (p === 67) return f(p, V.PERMANENTLY_RESET);\n    if (p === 1000) return f(p, b2v(mouseProtocol === 'VT200'));\n    if (p === 1002) return f(p, b2v(mouseProtocol === 'DRAG'));\n    if (p === 1003) return f(p, b2v(mouseProtocol === 'ANY'));\n    if (p === 1004) return f(p, b2v(dm.sendFocus));\n    if (p === 1005) return f(p, V.PERMANENTLY_RESET);\n    if (p === 1006) return f(p, b2v(mouseEncoding === 'SGR'));\n    if (p === 1015) return f(p, V.PERMANENTLY_RESET);\n    if (p === 1016) return f(p, b2v(mouseEncoding === 'SGR_PIXELS'));\n    if (p === 1048) return f(p, V.SET); // xterm always returns SET here\n    if (p === 47 || p === 1047 || p === 1049) return f(p, b2v(active === alt));\n    if (p === 2004) return f(p, b2v(dm.bracketedPasteMode));\n    if (p === 2026) return f(p, b2v(dm.synchronizedOutput));\n    if (p === 9001) return this._optionsService.rawOptions.vtExtensions?.win32InputMode ? f(p, b2v(dm.win32InputMode)) : f(p, V.NOT_RECOGNIZED);\n    return f(p, V.NOT_RECOGNIZED);\n  }\n\n  /**\n   * Helper to write color information packed with color mode.\n   */\n  private _updateAttrColor(color: number, mode: number, c1: number, c2: number, c3: number): number {\n    if (mode === 2) {\n      color |= Attributes.CM_RGB;\n      color &= ~Attributes.RGB_MASK;\n      color |= AttributeData.fromColorRGB([c1, c2, c3]);\n    } else if (mode === 5) {\n      color &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n      color |= Attributes.CM_P256 | (c1 & 0xff);\n    }\n    return color;\n  }\n\n  /**\n   * Helper to extract and apply color params/subparams.\n   * Returns advance for params index.\n   */\n  private _extractColor(params: IParams, pos: number, attr: IAttributeData): number {\n    // normalize params\n    // meaning: [target, CM, ign, val, val, val]\n    // RGB    : [ 38/48,  2, ign,   r,   g,   b]\n    // P256   : [ 38/48,  5, ign,   v, ign, ign]\n    const accu = [0, 0, -1, 0, 0, 0];\n\n    // alignment placeholder for non color space sequences\n    let cSpace = 0;\n\n    // return advance we took in params\n    let advance = 0;\n\n    do {\n      accu[advance + cSpace] = params.params[pos + advance];\n      if (params.hasSubParams(pos + advance)) {\n        const subparams = params.getSubParams(pos + advance)!;\n        let i = 0;\n        do {\n          if (accu[1] === 5) {\n            cSpace = 1;\n          }\n          accu[advance + i + 1 + cSpace] = subparams[i];\n        } while (++i < subparams.length && i + advance + 1 + cSpace < accu.length);\n        break;\n      }\n      // exit early if can decide color mode with semicolons\n      if ((accu[1] === 5 && advance + cSpace >= 2)\n        || (accu[1] === 2 && advance + cSpace >= 5)) {\n        break;\n      }\n      // offset colorSpace slot for semicolon mode\n      if (accu[1]) {\n        cSpace = 1;\n      }\n    } while (++advance + pos < params.length && advance + cSpace < accu.length);\n\n    // set default values to 0\n    for (let i = 2; i < accu.length; ++i) {\n      if (accu[i] === -1) {\n        accu[i] = 0;\n      }\n    }\n\n    // apply colors\n    switch (accu[0]) {\n      case 38:\n        attr.fg = this._updateAttrColor(attr.fg, accu[1], accu[3], accu[4], accu[5]);\n        break;\n      case 48:\n        attr.bg = this._updateAttrColor(attr.bg, accu[1], accu[3], accu[4], accu[5]);\n        break;\n      case 58:\n        attr.extended = attr.extended.clone();\n        attr.extended.underlineColor = this._updateAttrColor(attr.extended.underlineColor, accu[1], accu[3], accu[4], accu[5]);\n    }\n\n    return advance;\n  }\n\n  /**\n   * SGR 4 subparams:\n   *    4:0   -   equal to SGR 24 (turn off all underline)\n   *    4:1   -   equal to SGR 4 (single underline)\n   *    4:2   -   equal to SGR 21 (double underline)\n   *    4:3   -   curly underline\n   *    4:4   -   dotted underline\n   *    4:5   -   dashed underline\n   */\n  private _processUnderline(style: number, attr: IAttributeData): void {\n    // treat extended attrs as immutable, thus always clone from old one\n    // this is needed since the buffer only holds references to it\n    attr.extended = attr.extended.clone();\n\n    // default to 1 == single underline\n    if (!~style || style > 5) {\n      style = 1;\n    }\n    attr.extended.underlineStyle = style;\n    attr.fg |= FgFlags.UNDERLINE;\n\n    // 0 deactivates underline\n    if (style === 0) {\n      attr.fg &= ~FgFlags.UNDERLINE;\n    }\n\n    // update HAS_EXTENDED in BG\n    attr.updateExtended();\n  }\n\n  private _processSGR0(attr: IAttributeData): void {\n    attr.fg = DEFAULT_ATTR_DATA.fg;\n    attr.bg = DEFAULT_ATTR_DATA.bg;\n    attr.extended = attr.extended.clone();\n    // Reset underline style and color. Note that we don't want to reset other\n    // fields such as the url id.\n    attr.extended.underlineStyle = UnderlineStyle.NONE;\n    attr.extended.underlineColor &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n    attr.updateExtended();\n  }\n\n  /**\n   * CSI Pm m  Character Attributes (SGR).\n   *\n   * @vt: #P[See below for supported attributes.]    CSI SGR   \"Select Graphic Rendition\"  \"CSI Pm m\"  \"Set/Reset various text attributes.\"\n   * SGR selects one or more character attributes at the same time. Multiple params (up to 32)\n   * are applied in order from left to right. The changed attributes are applied to all new\n   * characters received. If you move characters in the viewport by scrolling or any other means,\n   * then the attributes move with the characters.\n   *\n   * Supported param values by SGR:\n   *\n   * | Param     | Meaning                                                  | Support |\n   * | --------- | -------------------------------------------------------- | ------- |\n   * | 0         | Normal (default). Resets any other preceding SGR.        | #Y      |\n   * | 1         | Bold. (also see `options.drawBoldTextInBrightColors`)    | #Y      |\n   * | 2         | Faint, decreased intensity.                              | #Y      |\n   * | 3         | Italic.                                                  | #Y      |\n   * | 4         | Underlined (see below for style support).                | #Y      |\n   * | 5         | Slowly blinking.                                         | #N      |\n   * | 6         | Rapidly blinking.                                        | #N      |\n   * | 7         | Inverse. Flips foreground and background color.          | #Y      |\n   * | 8         | Invisible (hidden).                                      | #Y      |\n   * | 9         | Crossed-out characters (strikethrough).                  | #Y      |\n   * | 21        | Doubly underlined.                                       | #Y      |\n   * | 22        | Normal (neither bold nor faint).                         | #Y      |\n   * | 23        | No italic.                                               | #Y      |\n   * | 24        | Not underlined.                                          | #Y      |\n   * | 25        | Steady (not blinking).                                   | #Y      |\n   * | 27        | Positive (not inverse).                                  | #Y      |\n   * | 28        | Visible (not hidden).                                    | #Y      |\n   * | 29        | Not Crossed-out (strikethrough).                         | #Y      |\n   * | 30        | Foreground color: Black.                                 | #Y      |\n   * | 31        | Foreground color: Red.                                   | #Y      |\n   * | 32        | Foreground color: Green.                                 | #Y      |\n   * | 33        | Foreground color: Yellow.                                | #Y      |\n   * | 34        | Foreground color: Blue.                                  | #Y      |\n   * | 35        | Foreground color: Magenta.                               | #Y      |\n   * | 36        | Foreground color: Cyan.                                  | #Y      |\n   * | 37        | Foreground color: White.                                 | #Y      |\n   * | 38        | Foreground color: Extended color.                        | #P[Support for RGB and indexed colors, see below.] |\n   * | 39        | Foreground color: Default (original).                    | #Y      |\n   * | 40        | Background color: Black.                                 | #Y      |\n   * | 41        | Background color: Red.                                   | #Y      |\n   * | 42        | Background color: Green.                                 | #Y      |\n   * | 43        | Background color: Yellow.                                | #Y      |\n   * | 44        | Background color: Blue.                                  | #Y      |\n   * | 45        | Background color: Magenta.                               | #Y      |\n   * | 46        | Background color: Cyan.                                  | #Y      |\n   * | 47        | Background color: White.                                 | #Y      |\n   * | 48        | Background color: Extended color.                        | #P[Support for RGB and indexed colors, see below.] |\n   * | 49        | Background color: Default (original).                    | #Y      |\n   * | 53        | Overlined.                                               | #Y      |\n   * | 55        | Not Overlined.                                           | #Y      |\n   * | 58        | Underline color: Extended color.                         | #P[Support for RGB and indexed colors, see below.] |\n   * | 221       | Not bold (kitty extension).                              | #Y      |\n   * | 222       | Not faint (kitty extension).                             | #Y      |\n   * | 90 - 97   | Bright foreground color (analogous to 30 - 37).          | #Y      |\n   * | 100 - 107 | Bright background color (analogous to 40 - 47).          | #Y      |\n   *\n   * Underline supports subparams to denote the style in the form `4 : x`:\n   *\n   * | x      | Meaning                                                       | Support |\n   * | ------ | ------------------------------------------------------------- | ------- |\n   * | 0      | No underline. Same as `SGR 24 m`.                             | #Y      |\n   * | 1      | Single underline. Same as `SGR 4 m`.                          | #Y      |\n   * | 2      | Double underline.                                             | #Y      |\n   * | 3      | Curly underline.                                              | #Y      |\n   * | 4      | Dotted underline.                                             | #Y      |\n   * | 5      | Dashed underline.                                             | #Y      |\n   * | other  | Single underline. Same as `SGR 4 m`.                          | #Y      |\n   *\n   * Extended colors are supported for foreground (Ps=38), background (Ps=48) and underline (Ps=58)\n   * as follows:\n   *\n   * | Ps + 1 | Meaning                                                       | Support |\n   * | ------ | ------------------------------------------------------------- | ------- |\n   * | 0      | Implementation defined.                                       | #N      |\n   * | 1      | Transparent.                                                  | #N      |\n   * | 2      | RGB color as `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`.  | #Y      |\n   * | 3      | CMY color.                                                    | #N      |\n   * | 4      | CMYK color.                                                   | #N      |\n   * | 5      | Indexed (256 colors) as `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | #Y      |\n   *\n   *\n   * FIXME: blinking is implemented in attrs, but not working in renderers?\n   * FIXME: remove dead branch for p=100\n   */\n  public charAttributes(params: IParams): boolean {\n    // Optimize a single SGR0.\n    if (params.length === 1 && params.params[0] === 0) {\n      this._processSGR0(this._curAttrData);\n      return true;\n    }\n\n    const l = params.length;\n    let p;\n    const attr = this._curAttrData;\n\n    for (let i = 0; i < l; i++) {\n      p = params.params[i];\n      if (p >= 30 && p <= 37) {\n        // fg color 8\n        attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n        attr.fg |= Attributes.CM_P16 | (p - 30);\n      } else if (p >= 40 && p <= 47) {\n        // bg color 8\n        attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n        attr.bg |= Attributes.CM_P16 | (p - 40);\n      } else if (p >= 90 && p <= 97) {\n        // fg color 16\n        attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n        attr.fg |= Attributes.CM_P16 | (p - 90) | 8;\n      } else if (p >= 100 && p <= 107) {\n        // bg color 16\n        attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n        attr.bg |= Attributes.CM_P16 | (p - 100) | 8;\n      } else if (p === 0) {\n        // default\n        this._processSGR0(attr);\n      } else if (p === 1) {\n        // bold text\n        attr.fg |= FgFlags.BOLD;\n      } else if (p === 3) {\n        // italic text\n        attr.bg |= BgFlags.ITALIC;\n      } else if (p === 4) {\n        // underlined text\n        attr.fg |= FgFlags.UNDERLINE;\n        this._processUnderline(params.hasSubParams(i) ? params.getSubParams(i)![0] : UnderlineStyle.SINGLE, attr);\n      } else if (p === 5) {\n        // blink\n        attr.fg |= FgFlags.BLINK;\n      } else if (p === 7) {\n        // inverse and positive\n        // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n        attr.fg |= FgFlags.INVERSE;\n      } else if (p === 8) {\n        // invisible\n        attr.fg |= FgFlags.INVISIBLE;\n      } else if (p === 9) {\n        // strikethrough\n        attr.fg |= FgFlags.STRIKETHROUGH;\n      } else if (p === 2) {\n        // dimmed text\n        attr.bg |= BgFlags.DIM;\n      } else if (p === 21) {\n        // double underline\n        this._processUnderline(UnderlineStyle.DOUBLE, attr);\n      } else if (p === 22) {\n        // not bold nor faint\n        attr.fg &= ~FgFlags.BOLD;\n        attr.bg &= ~BgFlags.DIM;\n      } else if (p === 23) {\n        // not italic\n        attr.bg &= ~BgFlags.ITALIC;\n      } else if (p === 24) {\n        // not underlined\n        attr.fg &= ~FgFlags.UNDERLINE;\n        this._processUnderline(UnderlineStyle.NONE, attr);\n      } else if (p === 25) {\n        // not blink\n        attr.fg &= ~FgFlags.BLINK;\n      } else if (p === 27) {\n        // not inverse\n        attr.fg &= ~FgFlags.INVERSE;\n      } else if (p === 28) {\n        // not invisible\n        attr.fg &= ~FgFlags.INVISIBLE;\n      } else if (p === 29) {\n        // not strikethrough\n        attr.fg &= ~FgFlags.STRIKETHROUGH;\n      } else if (p === 39) {\n        // reset fg\n        attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n        attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);\n      } else if (p === 49) {\n        // reset bg\n        attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n        attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);\n      } else if (p === 38 || p === 48 || p === 58) {\n        // fg color 256 and RGB\n        i += this._extractColor(params, i, attr);\n      } else if (p === 53) {\n        // overline\n        attr.bg |= BgFlags.OVERLINE;\n      } else if (p === 55) {\n        // not overline\n        attr.bg &= ~BgFlags.OVERLINE;\n      } else if (p === 221 && (this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl ?? true)) {\n        // not bold (kitty extension)\n        attr.fg &= ~FgFlags.BOLD;\n      } else if (p === 222 && (this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl ?? true)) {\n        // not faint (kitty extension)\n        attr.bg &= ~BgFlags.DIM;\n      } else if (p === 59) {\n        attr.extended = attr.extended.clone();\n        attr.extended.underlineColor = -1;\n        attr.updateExtended();\n      } else {\n        this._logService.debug('Unknown SGR attribute: %d.', p);\n      }\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps n  Device Status Report (DSR).\n   *     Ps = 5  -> Status Report.  Result (``OK'') is\n   *   CSI 0 n\n   *     Ps = 6  -> Report Cursor Position (CPR) [row;column].\n   *   Result is\n   *   CSI r ; c R\n   * CSI ? Ps n\n   *   Device Status Report (DSR, DEC-specific).\n   *     Ps = 6  -> Report Cursor Position (CPR) [row;column] as CSI\n   *     ? r ; c R (assumes page is zero).\n   *     Ps = 1 5  -> Report Printer status as CSI ? 1 0  n  (ready).\n   *     or CSI ? 1 1  n  (not ready).\n   *     Ps = 2 5  -> Report UDK status as CSI ? 2 0  n  (unlocked)\n   *     or CSI ? 2 1  n  (locked).\n   *     Ps = 2 6  -> Report Keyboard status as\n   *   CSI ? 2 7  ;  1  ;  0  ;  0  n  (North American).\n   *   The last two parameters apply to VT400 & up, and denote key-\n   *   board ready and LK01 respectively.\n   *     Ps = 5 3  -> Report Locator status as\n   *   CSI ? 5 3  n  Locator available, if compiled-in, or\n   *   CSI ? 5 0  n  No Locator, if not.\n   *\n   * @vt: #Y CSI DSR   \"Device Status Report\"  \"CSI Ps n\"  \"Request cursor position (CPR) with `Ps` = 6.\"\n   */\n  public deviceStatus(params: IParams): boolean {\n    switch (params.params[0]) {\n      case 5:\n        // status report\n        this._coreService.triggerDataEvent(`${C0.ESC}[0n`);\n        break;\n      case 6:\n        // cursor position\n        const y = this._activeBuffer.y + 1;\n        const x = this._activeBuffer.x + 1;\n        this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`);\n        break;\n    }\n    return true;\n  }\n\n  // @vt: #P[Only CPR is supported.]  CSI DECDSR  \"DEC Device Status Report\"  \"CSI ? Ps n\"  \"Only CPR is supported (same as DSR).\"\n  public deviceStatusPrivate(params: IParams): boolean {\n    // modern xterm doesnt seem to\n    // respond to any of these except ?6, 6, and 5\n    switch (params.params[0]) {\n      case 6:\n        // cursor position\n        const y = this._activeBuffer.y + 1;\n        const x = this._activeBuffer.x + 1;\n        this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`);\n        break;\n      case 15:\n        // no printer\n        // this.handler(C0.ESC + '[?11n');\n        break;\n      case 25:\n        // dont support user defined keys\n        // this.handler(C0.ESC + '[?21n');\n        break;\n      case 26:\n        // north american keyboard\n        // this.handler(C0.ESC + '[?27;1;0;0n');\n        break;\n      case 53:\n        // no dec locator/mouse\n        // this.handler(C0.ESC + '[?50n');\n        break;\n      case 996:\n        // color scheme query (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)\n        if (this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery ?? true) {\n          this._onRequestColorSchemeQuery.fire();\n        }\n        break;\n    }\n    return true;\n  }\n\n  /**\n   * CSI ! p   Soft terminal reset (DECSTR).\n   * http://vt100.net/docs/vt220-rm/table4-10.html\n   *\n   * @vt: #Y CSI DECSTR  \"Soft Terminal Reset\"   \"CSI ! p\"   \"Reset several terminal attributes to initial state.\"\n   * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full\n   * terminal bootstrap, DECSTR only resets certain attributes. For most needs DECSTR should be\n   * sufficient.\n   *\n   * The following terminal attributes are reset to default values:\n   * - IRM is reset (dafault = false)\n   * - scroll margins are reset (default = viewport size)\n   * - erase attributes are reset to default\n   * - charsets are reset\n   * - DECSC data is reset to initial values\n   * - DECOM is reset to absolute mode\n   *\n   *\n   * FIXME: there are several more attributes missing (see VT520 manual)\n   */\n  public softReset(params: IParams): boolean {\n    this._coreService.isCursorHidden = false;\n    this._onRequestSyncScrollBar.fire();\n    this._activeBuffer.scrollTop = 0;\n    this._activeBuffer.scrollBottom = this._bufferService.rows - 1;\n    this._curAttrData = DEFAULT_ATTR_DATA.clone();\n    this._coreService.reset();\n    this._charsetService.reset();\n\n    // reset DECSC data\n    this._activeBuffer.savedX = 0;\n    this._activeBuffer.savedY = this._activeBuffer.ybase;\n    this._activeBuffer.savedCurAttrData.fg = this._curAttrData.fg;\n    this._activeBuffer.savedCurAttrData.bg = this._curAttrData.bg;\n    this._activeBuffer.savedCharset = this._charsetService.charset;\n\n    // reset DECOM\n    this._coreService.decPrivateModes.origin = false;\n    return true;\n  }\n\n  /**\n   * CSI Ps SP q  Set cursor style (DECSCUSR, VT520).\n   *   Ps = 0  -> reset to option.\n   *   Ps = 1  -> blinking block (default).\n   *   Ps = 2  -> steady block.\n   *   Ps = 3  -> blinking underline.\n   *   Ps = 4  -> steady underline.\n   *   Ps = 5  -> blinking bar (xterm).\n   *   Ps = 6  -> steady bar (xterm).\n   *\n   * @vt: #Y CSI DECSCUSR  \"Set Cursor Style\"  \"CSI Ps SP q\"   \"Set cursor style.\"\n   * Supported cursor styles:\n   *  - 0: reset to option\n   *  - empty, 1: blinking block\n   *  - 2: steady block\n   *  - 3: blinking underline\n   *  - 4: steady underline\n   *  - 5: blinking bar\n   *  - 6: steady bar\n   */\n  public setCursorStyle(params: IParams): boolean {\n    const param = params.length === 0 ? 1 : params.params[0];\n    if (param === 0) {\n      this._coreService.decPrivateModes.cursorStyle = undefined;\n      this._coreService.decPrivateModes.cursorBlink = undefined;\n    } else {\n      switch (param) {\n        case 1:\n        case 2:\n          this._coreService.decPrivateModes.cursorStyle = 'block';\n          break;\n        case 3:\n        case 4:\n          this._coreService.decPrivateModes.cursorStyle = 'underline';\n          break;\n        case 5:\n        case 6:\n          this._coreService.decPrivateModes.cursorStyle = 'bar';\n          break;\n      }\n      const isBlinking = param % 2 === 1;\n      this._coreService.decPrivateModes.cursorBlink = isBlinking;\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps ; Ps r\n   *   Set Scrolling Region [top;bottom] (default = full size of win-\n   *   dow) (DECSTBM).\n   *\n   * @vt: #Y CSI DECSTBM \"Set Top and Bottom Margin\" \"CSI Ps ; Ps r\" \"Set top and bottom margins of the viewport [top;bottom] (default = viewport size).\"\n   */\n  public setScrollRegion(params: IParams): boolean {\n    const top = params.params[0] || 1;\n    let bottom: number;\n\n    if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {\n      bottom = this._bufferService.rows;\n    }\n\n    if (bottom > top) {\n      this._activeBuffer.scrollTop = top - 1;\n      this._activeBuffer.scrollBottom = bottom - 1;\n      this._setCursor(0, 0);\n    }\n    return true;\n  }\n\n  /**\n   * CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm)\n   *\n   * Note: Only those listed below are supported. All others are left to integrators and\n   * need special treatment based on the embedding environment.\n   *\n   *    Ps = 1 4                                                          supported\n   *      Report xterm text area size in pixels.\n   *      Result is CSI 4 ; height ; width t\n   *    Ps = 14 ; 2                                                       not implemented\n   *    Ps = 16                                                           supported\n   *      Report xterm character cell size in pixels.\n   *      Result is CSI 6 ; height ; width t\n   *    Ps = 18                                                           supported\n   *      Report the size of the text area in characters.\n   *      Result is CSI 8 ; height ; width t\n   *    Ps = 20                                                           supported\n   *      Report xterm window's icon label.\n   *      Result is OSC L label ST\n   *    Ps = 21                                                           supported\n   *      Report xterm window's title.\n   *      Result is OSC l label ST\n   *    Ps = 22 ; 0  -> Save xterm icon and window title on stack.        supported\n   *    Ps = 22 ; 1  -> Save xterm icon title on stack.                   supported\n   *    Ps = 22 ; 2  -> Save xterm window title on stack.                 supported\n   *    Ps = 23 ; 0  -> Restore xterm icon and window title from stack.   supported\n   *    Ps = 23 ; 1  -> Restore xterm icon title from stack.              supported\n   *    Ps = 23 ; 2  -> Restore xterm window title from stack.            supported\n   *    Ps >= 24                                                          not implemented\n   */\n  public windowOptions(params: IParams): boolean {\n    if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) {\n      return true;\n    }\n    const second = (params.length > 1) ? params.params[1] : 0;\n    switch (params.params[0]) {\n      case 14:  // GetWinSizePixels, returns CSI 4 ; height ; width t\n        if (second !== 2) {\n          this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS);\n        }\n        break;\n      case 16:  // GetCellSizePixels, returns CSI 6 ; height ; width t\n        this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS);\n        break;\n      case 18:  // GetWinSizeChars, returns CSI 8 ; height ; width t\n        if (this._bufferService) {\n          this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);\n        }\n        break;\n      case 22:  // PushTitle\n        if (second === 0 || second === 2) {\n          this._windowTitleStack.push(this._windowTitle);\n          if (this._windowTitleStack.length > STACK_LIMIT) {\n            this._windowTitleStack.shift();\n          }\n        }\n        if (second === 0 || second === 1) {\n          this._iconNameStack.push(this._iconName);\n          if (this._iconNameStack.length > STACK_LIMIT) {\n            this._iconNameStack.shift();\n          }\n        }\n        break;\n      case 23:  // PopTitle\n        if (second === 0 || second === 2) {\n          if (this._windowTitleStack.length) {\n            this.setTitle(this._windowTitleStack.pop()!);\n          }\n        }\n        if (second === 0 || second === 1) {\n          if (this._iconNameStack.length) {\n            this.setIconName(this._iconNameStack.pop()!);\n          }\n        }\n        break;\n    }\n    return true;\n  }\n\n\n  /**\n   * CSI s\n   * ESC 7\n   *   Save cursor (ANSI.SYS).\n   *\n   * @vt: #P[TODO...]  CSI SCOSC   \"Save Cursor\"   \"CSI s\"   \"Save cursor position, charmap and text attributes.\"\n   * @vt: #Y ESC  SC   \"Save Cursor\"   \"ESC 7\"   \"Save cursor position, charmap and text attributes.\"\n   */\n  public saveCursor(params?: IParams): boolean {\n    this._activeBuffer.savedX = this._activeBuffer.x;\n    this._activeBuffer.savedY = this._activeBuffer.ybase + this._activeBuffer.y;\n    this._activeBuffer.savedCurAttrData.fg = this._curAttrData.fg;\n    this._activeBuffer.savedCurAttrData.bg = this._curAttrData.bg;\n    this._activeBuffer.savedCharset = this._charsetService.charset;\n    this._activeBuffer.savedCharsets = this._charsetService.charsets.slice();\n    this._activeBuffer.savedGlevel = this._charsetService.glevel;\n    this._activeBuffer.savedOriginMode = this._coreService.decPrivateModes.origin;\n    this._activeBuffer.savedWraparoundMode = this._coreService.decPrivateModes.wraparound;\n    return true;\n  }\n\n\n  /**\n   * CSI u\n   * ESC 8\n   *   Restore cursor (ANSI.SYS).\n   *\n   * @vt: #P[TODO...]  CSI SCORC \"Restore Cursor\"  \"CSI u\"   \"Restore cursor position, charmap and text attributes.\"\n   * @vt: #Y ESC  RC \"Restore Cursor\"  \"ESC 8\"   \"Restore cursor position, charmap and text attributes.\"\n   */\n  public restoreCursor(params?: IParams): boolean {\n    this._activeBuffer.x = this._activeBuffer.savedX || 0;\n    this._activeBuffer.y = Math.max(this._activeBuffer.savedY - this._activeBuffer.ybase, 0);\n    this._curAttrData.fg = this._activeBuffer.savedCurAttrData.fg;\n    this._curAttrData.bg = this._activeBuffer.savedCurAttrData.bg;\n    for (let i = 0; i < this._activeBuffer.savedCharsets.length; i++) {\n      this._charsetService.setgCharset(i, this._activeBuffer.savedCharsets[i]);\n    }\n    this._charsetService.setgLevel(this._activeBuffer.savedGlevel);\n    this._coreService.decPrivateModes.origin = this._activeBuffer.savedOriginMode;\n    this._coreService.decPrivateModes.wraparound = this._activeBuffer.savedWraparoundMode;\n    this._restrictCursor();\n    return true;\n  }\n\n  /**\n   * OSC 2; <data> ST (set window title)\n   *   Proxy to set window title.\n   *\n   * @vt: #P[Icon name is not exposed.]   OSC    0   \"Set Windows Title and Icon Name\"  \"OSC 0 ; Pt BEL\"  \"Set window title and icon name.\"\n   * Icon name is not supported. For Window Title see below.\n   *\n   * @vt: #Y     OSC    2   \"Set Windows Title\"  \"OSC 2 ; Pt BEL\"  \"Set window title.\"\n   * xterm.js does not manipulate the title directly, instead exposes changes via the event\n   * `Terminal.onTitleChange`.\n   */\n  public setTitle(data: string): boolean {\n    this._windowTitle = data;\n    this._onTitleChange.fire(data);\n    return true;\n  }\n\n  /**\n   * OSC 1; <data> ST\n   * Note: Icon name is not exposed.\n   */\n  public setIconName(data: string): boolean {\n    this._iconName = data;\n    return true;\n  }\n\n  /**\n   * OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)\n   *\n   * @vt: #Y    OSC    4    \"Set ANSI color\"   \"OSC 4 ; c ; spec BEL\" \"Change color number `c` to the color specified by `spec`.\"\n   * `c` is the color index between 0 and 255. The color format of `spec` is derived from\n   * `XParseColor` (see OSC 10 for supported formats). There may be multipe `c ; spec` pairs present\n   * in the same instruction. If `spec` contains `?` the terminal returns a sequence with the\n   * currently set color.\n   */\n  public setOrReportIndexedColor(data: string): boolean {\n    const event: IColorEvent = [];\n    const slots = data.split(';');\n    while (slots.length > 1) {\n      const idx = slots.shift() as string;\n      const spec = slots.shift() as string;\n      if (/^\\d+$/.exec(idx)) {\n        const index = parseInt(idx);\n        if (isValidColorIndex(index)) {\n          if (spec === '?') {\n            event.push({ type: ColorRequestType.REPORT, index });\n          } else {\n            const color = parseColor(spec);\n            if (color) {\n              event.push({ type: ColorRequestType.SET, index, color });\n            }\n          }\n        }\n      }\n    }\n    if (event.length) {\n      this._onColor.fire(event);\n    }\n    return true;\n  }\n\n  /**\n   * OSC 8 ; <params> ; <uri> ST - create hyperlink\n   * OSC 8 ; ; ST - finish hyperlink\n   *\n   * Test case:\n   *\n   * ```sh\n   * printf '\\e]8;;http://example.com\\e\\\\This is a link\\e]8;;\\e\\\\\\n'\n   * ```\n   *\n   * @vt: #Y    OSC    8    \"Create hyperlink\"   \"OSC 8 ; params ; uri BEL\" \"Create a hyperlink to `uri` using `params`.\"\n   * `uri` is a hyperlink starting with `http://`, `https://`, `ftp://`, `file://` or `mailto://`. `params` is an\n   * optional list of key=value assignments, separated by the : character.\n   * Example: `id=xyz123:foo=bar:baz=quux`.\n   * Currently only the id key is defined. Cells that share the same ID and URI share hover\n   * feedback. Use `OSC 8 ; ; BEL` to finish the current hyperlink.\n   */\n  public setHyperlink(data: string): boolean {\n    // Arg parsing is special cases to support unencoded semi-colons in the URIs (#4944)\n    const idx = data.indexOf(';');\n    if (idx === -1) {\n      // malformed sequence, just return as handled\n      return true;\n    }\n    const id = data.slice(0, idx).trim();\n    const uri = data.slice(idx + 1);\n    if (uri) {\n      return this._createHyperlink(id, uri);\n    }\n    if (id.trim()) {\n      return false;\n    }\n    return this._finishHyperlink();\n  }\n\n  private _createHyperlink(params: string, uri: string): boolean {\n    // It's legal to open a new hyperlink without explicitly finishing the previous one\n    if (this._getCurrentLinkId()) {\n      this._finishHyperlink();\n    }\n    const parsedParams = params.split(':');\n    let id: string | undefined;\n    const idParamIndex = parsedParams.findIndex(e => e.startsWith('id='));\n    if (idParamIndex !== -1) {\n      id = parsedParams[idParamIndex].slice(3) || undefined;\n    }\n    this._curAttrData.extended = this._curAttrData.extended.clone();\n    this._curAttrData.extended.urlId = this._oscLinkService.registerLink({ id, uri });\n    this._curAttrData.updateExtended();\n    return true;\n  }\n\n  private _finishHyperlink(): boolean {\n    this._curAttrData.extended = this._curAttrData.extended.clone();\n    this._curAttrData.extended.urlId = 0;\n    this._curAttrData.updateExtended();\n    return true;\n  }\n\n  // special colors - OSC 10 | 11 | 12\n  private _specialColors = [SpecialColorIndex.FOREGROUND, SpecialColorIndex.BACKGROUND, SpecialColorIndex.CURSOR];\n\n  /**\n   * Apply colors requests for special colors in OSC 10 | 11 | 12.\n   * Since these commands are stacking from multiple parameters,\n   * we handle them in a loop with an entry offset to `_specialColors`.\n   */\n  private _setOrReportSpecialColor(data: string, offset: number): boolean {\n    const slots = data.split(';');\n    for (let i = 0; i < slots.length; ++i, ++offset) {\n      if (offset >= this._specialColors.length) break;\n      if (slots[i] === '?') {\n        this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]);\n      } else {\n        const color = parseColor(slots[i]);\n        if (color) {\n          this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]);\n        }\n      }\n    }\n    return true;\n  }\n\n  /**\n   * OSC 10 ; <xcolor name>|<?> ST - set or query default foreground color\n   *\n   * @vt: #Y  OSC   10    \"Set or query default foreground color\"   \"OSC 10 ; Pt BEL\"  \"Set or query default foreground color.\"\n   * To set the color, the following color specification formats are supported:\n   * - `rgb:<red>/<green>/<blue>` for  `<red>, <green>, <blue>` in `h | hh | hhh | hhhh`, where\n   *   `h` is a single hexadecimal digit (case insignificant). The different widths scale\n   *   from 4 bit (`h`) to 16 bit (`hhhh`) and get converted to 8 bit (`hh`).\n   * - `#RGB` - 4 bits per channel, expanded to `#R0G0B0`\n   * - `#RRGGBB` - 8 bits per channel\n   * - `#RRRGGGBBB` - 12 bits per channel, truncated to `#RRGGBB`\n   * - `#RRRRGGGGBBBB` - 16 bits per channel, truncated to `#RRGGBB`\n   *\n   * **Note:** X11 named colors are currently unsupported.\n   *\n   * If `Pt` contains `?` instead of a color specification, the terminal\n   * returns a sequence with the current default foreground color\n   * (use that sequence to restore the color after changes).\n   *\n   * **Note:** Other than xterm, xterm.js does not support OSC 12 - 19.\n   * Therefore stacking multiple `Pt` separated by `;` only works for the first two entries.\n   */\n  public setOrReportFgColor(data: string): boolean {\n    return this._setOrReportSpecialColor(data, 0);\n  }\n\n  /**\n   * OSC 11 ; <xcolor name>|<?> ST - set or query default background color\n   *\n   * @vt: #Y  OSC   11    \"Set or query default background color\"   \"OSC 11 ; Pt BEL\"  \"Same as OSC 10, but for default background.\"\n   */\n  public setOrReportBgColor(data: string): boolean {\n    return this._setOrReportSpecialColor(data, 1);\n  }\n\n  /**\n   * OSC 12 ; <xcolor name>|<?> ST - set or query default cursor color\n   *\n   * @vt: #Y  OSC   12    \"Set or query default cursor color\"   \"OSC 12 ; Pt BEL\"  \"Same as OSC 10, but for default cursor color.\"\n   */\n  public setOrReportCursorColor(data: string): boolean {\n    return this._setOrReportSpecialColor(data, 2);\n  }\n\n  /**\n   * OSC 104 ; <num> ST - restore ANSI color <num>\n   *\n   * @vt: #Y  OSC   104    \"Reset ANSI color\"   \"OSC 104 ; c BEL\" \"Reset color number `c` to themed color.\"\n   * `c` is the color index between 0 and 255. This function restores the default color for `c` as\n   * specified by the loaded theme. Any number of `c` parameters may be given.\n   * If no parameters are given, the entire indexed color table will be reset.\n   */\n  public restoreIndexedColor(data: string): boolean {\n    if (!data) {\n      this._onColor.fire([{ type: ColorRequestType.RESTORE }]);\n      return true;\n    }\n    const event: IColorEvent = [];\n    const slots = data.split(';');\n    for (let i = 0; i < slots.length; ++i) {\n      if (/^\\d+$/.exec(slots[i])) {\n        const index = parseInt(slots[i]);\n        if (isValidColorIndex(index)) {\n          event.push({ type: ColorRequestType.RESTORE, index });\n        }\n      }\n    }\n    if (event.length) {\n      this._onColor.fire(event);\n    }\n    return true;\n  }\n\n  /**\n   * OSC 110 ST - restore default foreground color\n   *\n   * @vt: #Y  OSC   110    \"Restore default foreground color\"   \"OSC 110 BEL\"  \"Restore default foreground to themed color.\"\n   */\n  public restoreFgColor(data: string): boolean {\n    this._onColor.fire([{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.FOREGROUND }]);\n    return true;\n  }\n\n  /**\n   * OSC 111 ST - restore default background color\n   *\n   * @vt: #Y  OSC   111    \"Restore default background color\"   \"OSC 111 BEL\"  \"Restore default background to themed color.\"\n   */\n  public restoreBgColor(data: string): boolean {\n    this._onColor.fire([{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.BACKGROUND }]);\n    return true;\n  }\n\n  /**\n   * OSC 112 ST - restore default cursor color\n   *\n   * @vt: #Y  OSC   112    \"Restore default cursor color\"   \"OSC 112 BEL\"  \"Restore default cursor to themed color.\"\n   */\n  public restoreCursorColor(data: string): boolean {\n    this._onColor.fire([{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.CURSOR }]);\n    return true;\n  }\n\n  /**\n   * ESC E\n   * C1.NEL\n   *   DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL)\n   *   Moves cursor to first position on next line.\n   *\n   * @vt: #Y   C1    NEL   \"Next Line\"   \"\\x85\"    \"Move the cursor to the beginning of the next row.\"\n   * @vt: #Y   ESC   NEL   \"Next Line\"   \"ESC E\"   \"Move the cursor to the beginning of the next row.\"\n   */\n  public nextLine(): boolean {\n    this._activeBuffer.x = 0;\n    this.index();\n    return true;\n  }\n\n  /**\n   * ESC =\n   *   DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html)\n   *   Enables the numeric keypad to send application sequences to the host.\n   */\n  public keypadApplicationMode(): boolean {\n    this._logService.debug('Serial port requested application keypad.');\n    this._coreService.decPrivateModes.applicationKeypad = true;\n    this._onRequestSyncScrollBar.fire();\n    return true;\n  }\n\n  /**\n   * ESC >\n   *   DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html)\n   *   Enables the keypad to send numeric characters to the host.\n   */\n  public keypadNumericMode(): boolean {\n    this._logService.debug('Switching back to normal keypad.');\n    this._coreService.decPrivateModes.applicationKeypad = false;\n    this._onRequestSyncScrollBar.fire();\n    return true;\n  }\n\n  /**\n   * ESC % @\n   * ESC % G\n   *   Select default character set. UTF-8 is not supported (string are unicode anyways)\n   *   therefore ESC % G does the same.\n   */\n  public selectDefaultCharset(): boolean {\n    this._charsetService.setgLevel(0);\n    this._charsetService.setgCharset(0, DEFAULT_CHARSET); // US (default)\n    return true;\n  }\n\n  /**\n   * ESC ( C\n   *   Designate G0 Character Set, VT100, ISO 2022.\n   * ESC ) C\n   *   Designate G1 Character Set (ISO 2022, VT100).\n   * ESC * C\n   *   Designate G2 Character Set (ISO 2022, VT220).\n   * ESC + C\n   *   Designate G3 Character Set (ISO 2022, VT220).\n   * ESC - C\n   *   Designate G1 Character Set (VT300).\n   * ESC . C\n   *   Designate G2 Character Set (VT300).\n   * ESC / C\n   *   Designate G3 Character Set (VT300). C = A  -> ISO Latin-1 Supplemental. - Supported?\n   */\n  public selectCharset(collectAndFlag: string): boolean {\n    if (collectAndFlag.length !== 2) {\n      this.selectDefaultCharset();\n      return true;\n    }\n    if (collectAndFlag[0] === '/') {\n      return true;  // TODO: Is this supported?\n    }\n    this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] ?? DEFAULT_CHARSET);\n    return true;\n  }\n\n  /**\n   * ESC D\n   * C1.IND\n   *   DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html)\n   *   Moves the cursor down one line in the same column.\n   *\n   * @vt: #Y   C1    IND   \"Index\"   \"\\x84\"    \"Move the cursor one line down scrolling if needed.\"\n   * @vt: #Y   ESC   IND   \"Index\"   \"ESC D\"   \"Move the cursor one line down scrolling if needed.\"\n   */\n  public index(): boolean {\n    this._restrictCursor();\n    this._activeBuffer.y++;\n    if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {\n      this._activeBuffer.y--;\n      this._bufferService.scroll(this._eraseAttrData());\n    } else if (this._activeBuffer.y >= this._bufferService.rows) {\n      this._activeBuffer.y = this._bufferService.rows - 1;\n    }\n    this._restrictCursor();\n    return true;\n  }\n\n  /**\n   * ESC H\n   * C1.HTS\n   *   DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html)\n   *   Sets a horizontal tab stop at the column position indicated by\n   *   the value of the active column when the terminal receives an HTS.\n   *\n   * @vt: #Y   C1    HTS   \"Horizontal Tabulation Set\" \"\\x88\"    \"Places a tab stop at the current cursor position.\"\n   * @vt: #Y   ESC   HTS   \"Horizontal Tabulation Set\" \"ESC H\"   \"Places a tab stop at the current cursor position.\"\n   */\n  public tabSet(): boolean {\n    this._activeBuffer.tabs[this._activeBuffer.x] = true;\n    return true;\n  }\n\n  /**\n   * ESC M\n   * C1.RI\n   *   DEC mnemonic: HTS\n   *   Moves the cursor up one line in the same column. If the cursor is at the top margin,\n   *   the page scrolls down.\n   *\n   * @vt: #Y ESC  IR \"Reverse Index\" \"ESC M\"  \"Move the cursor one line up scrolling if needed.\"\n   */\n  public reverseIndex(): boolean {\n    this._restrictCursor();\n    if (this._activeBuffer.y === this._activeBuffer.scrollTop) {\n      // possibly move the code below to term.reverseScroll();\n      // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n      // blankLine(true) is xterm/linux behavior\n      const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop;\n      this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1);\n      this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData()));\n      this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);\n    } else {\n      this._activeBuffer.y--;\n      this._restrictCursor(); // quickfix to not run out of bounds\n    }\n    return true;\n  }\n\n  /**\n   * ESC c\n   *   DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html)\n   *   Reset to initial state.\n   *\n   * @vt: #Y ESC  RIS \"Full Reset\" \"ESC c\"  \"Reset to initial state.\"\n   */\n  public fullReset(): boolean {\n    this._parser.reset();\n    this._onRequestReset.fire();\n    return true;\n  }\n\n  public reset(): void {\n    this._curAttrData = DEFAULT_ATTR_DATA.clone();\n    this._eraseAttrDataInternal = DEFAULT_ATTR_DATA.clone();\n  }\n\n  /**\n   * back_color_erase feature for xterm.\n   */\n  private _eraseAttrData(): IAttributeData {\n    this._eraseAttrDataInternal.bg &= ~(Attributes.CM_MASK | 0xFFFFFF);\n    this._eraseAttrDataInternal.bg |= this._curAttrData.bg & ~0xFC000000;\n    return this._eraseAttrDataInternal;\n  }\n\n  /**\n   * ESC n\n   * ESC o\n   * ESC |\n   * ESC }\n   * ESC ~\n   *   DEC mnemonic: LS (https://vt100.net/docs/vt510-rm/LS.html)\n   *   When you use a locking shift, the character set remains in GL or GR until\n   *   you use another locking shift. (partly supported)\n   */\n  public setgLevel(level: number): boolean {\n    this._charsetService.setgLevel(level);\n    return true;\n  }\n\n  /**\n   * ESC # 8\n   *   DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html)\n   *   This control function fills the complete screen area with\n   *   a test pattern (E) used for adjusting screen alignment.\n   *\n   * @vt: #Y   ESC   DECALN   \"Screen Alignment Pattern\"  \"ESC # 8\"  \"Fill viewport with a test pattern (E).\"\n   */\n  public screenAlignmentPattern(): boolean {\n    // prepare cell data\n    const cell = new CellData();\n    cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0);\n    cell.fg = this._curAttrData.fg;\n    cell.bg = this._curAttrData.bg;\n\n\n    this._setCursor(0, 0);\n    for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) {\n      const row = this._activeBuffer.ybase + this._activeBuffer.y + yOffset;\n      const line = this._activeBuffer.lines.get(row);\n      if (line) {\n        line.fill(cell);\n        line.isWrapped = false;\n      }\n    }\n    this._dirtyRowTracker.markAllDirty();\n    this._setCursor(0, 0);\n    return true;\n  }\n\n\n  /**\n   * DCS $ q Pt ST\n   *   DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)\n   *   Request Status String (DECRQSS), VT420 and up.\n   *   Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)\n   *\n   * @vt: #P[Limited support, see below.]  DCS   DECRQSS   \"Request Selection or Setting\"  \"DCS $ q Pt ST\"   \"Request several terminal settings.\"\n   * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the\n   * corresponding CSI string, `ESC P 0 ST` for invalid requests.\n   *\n   * Supported requests and responses:\n   *\n   * | Type                             | Request           | Response (`Pt`)                                       |\n   * | -------------------------------- | ----------------- | ----------------------------------------------------- |\n   * | Graphic Rendition (SGR)          | `DCS $ q m ST`    | always reporting `0m` (currently broken)              |\n   * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST`    | `Ps ; Ps r`                                           |\n   * | Cursor Style (DECSCUSR)          | `DCS $ q SP q ST` | `Ps SP q`                                             |\n   * | Protection Attribute (DECSCA)    | `DCS $ q \" q ST`  | `Ps \" q` (DECSCA 2 is reported as Ps = 0)             |\n   * | Conformance Level (DECSCL)       | `DCS $ q \" p ST`  | always reporting `61 ; 1 \" p` (DECSCL is unsupported) |\n   *\n   *\n   * TODO:\n   * - fix SGR report\n   * - either check which conformance is better suited or remove the report completely\n   *   --> we are currently a mixture of all up to VT400 but dont follow anyone strictly\n   */\n  public requestStatusString(data: string, params: IParams): boolean {\n    const f = (s: string): boolean => {\n      this._coreService.triggerDataEvent(`${C0.ESC}${s}${C0.ESC}\\\\`);\n      return true;\n    };\n\n    // access helpers\n    const b = this._bufferService.buffer;\n    const opts = this._optionsService.rawOptions;\n    const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 };\n\n    if (data === '\"q') return f(`P1$r${this._curAttrData.isProtected() ? 1 : 0}\"q`);\n    if (data === '\"p') return f(`P1$r61;1\"p`);\n    if (data === 'r') return f(`P1$r${b.scrollTop + 1};${b.scrollBottom + 1}r`);\n    // FIXME: report real SGR settings instead of 0m\n    if (data === 'm') return f(`P1$r0m`);\n    if (data === ' q') return f(`P1$r${STYLES[opts.cursorStyle] - (opts.cursorBlink ? 1 : 0)} q`);\n    return f(`P0$r`);\n  }\n\n  public markRangeDirty(y1: number, y2: number): void {\n    this._dirtyRowTracker.markRangeDirty(y1, y2);\n  }\n\n  // #region Kitty keyboard\n\n  /**\n   * CSI = flags ; mode u\n   * Set Kitty keyboard protocol flags.\n   * mode: 1=set, 2=set-only-specified, 3=reset-only-specified\n   *\n   * @vt: #Y CSI KKBDSET \"Kitty Keyboard Set\" \"CSI = Ps ; Pm u\" \"Set Kitty keyboard protocol flags.\"\n   */\n  public kittyKeyboardSet(params: IParams): boolean {\n    if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {\n      return true;\n    }\n    const flags = params.params[0] || 0;\n    const mode = params.length > 1 ? (params.params[1] || 1) : 1;\n    const state = this._coreService.kittyKeyboard;\n\n    switch (mode) {\n      case 1: // Set all flags\n        state.flags = flags;\n        break;\n      case 2: // Set only specified flags (OR)\n        state.flags |= flags;\n        break;\n      case 3: // Reset only specified flags (AND NOT)\n        state.flags &= ~flags;\n        break;\n    }\n    return true;\n  }\n\n  /**\n   * CSI ? u\n   * Query Kitty keyboard protocol flags.\n   * Terminal responds with CSI ? flags u\n   *\n   * @vt: #Y CSI KKBDQUERY \"Kitty Keyboard Query\" \"CSI ? u\" \"Query Kitty keyboard protocol flags.\"\n   */\n  public kittyKeyboardQuery(params: IParams): boolean {\n    if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {\n      return true;\n    }\n    const flags = this._coreService.kittyKeyboard.flags;\n    this._coreService.triggerDataEvent(`${C0.ESC}[?${flags}u`);\n    return true;\n  }\n\n  /**\n   * CSI > flags u\n   * Push Kitty keyboard flags onto stack and set new flags.\n   *\n   * @vt: #Y CSI KKBDPUSH \"Kitty Keyboard Push\" \"CSI > Ps u\" \"Push keyboard flags to stack and set new flags.\"\n   */\n  public kittyKeyboardPush(params: IParams): boolean {\n    if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {\n      return true;\n    }\n    const flags = params.params[0] || 0;\n    const state = this._coreService.kittyKeyboard;\n    const isAlt = this._bufferService.buffer === this._bufferService.buffers.alt;\n    const stack = isAlt ? state.altStack : state.mainStack;\n\n    // Evict oldest entry if stack is full (DoS protection, limit of 16)\n    if (stack.length >= 16) {\n      stack.shift();\n    }\n\n    // Push current flags onto stack and set new flags\n    stack.push(state.flags);\n    state.flags = flags;\n    return true;\n  }\n\n  /**\n   * CSI < count u\n   * Pop Kitty keyboard flags from stack.\n   *\n   * @vt: #Y CSI KKBDPOP \"Kitty Keyboard Pop\" \"CSI < Ps u\" \"Pop keyboard flags from stack.\"\n   */\n  public kittyKeyboardPop(params: IParams): boolean {\n    if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {\n      return true;\n    }\n    const count = Math.max(1, params.params[0] || 1);\n    const state = this._coreService.kittyKeyboard;\n    const isAlt = this._bufferService.buffer === this._bufferService.buffers.alt;\n    const stack = isAlt ? state.altStack : state.mainStack;\n\n    // Pop specified number of entries from stack\n    for (let i = 0; i < count && stack.length > 0; i++) {\n      state.flags = stack.pop()!;\n    }\n    // If stack is empty after popping, reset to 0\n    if (stack.length === 0 && count > 0) {\n      state.flags = 0;\n    }\n    return true;\n  }\n\n  // #endregion\n}\n\nexport interface IDirtyRowTracker {\n  readonly start: number;\n  readonly end: number;\n\n  clearRange(): void;\n  markDirty(y: number): void;\n  markRangeDirty(y1: number, y2: number): void;\n  markAllDirty(): void;\n}\n\nclass DirtyRowTracker implements IDirtyRowTracker {\n  public start!: number;\n  public end!: number;\n\n  constructor(\n    @IBufferService private readonly _bufferService: IBufferService\n  ) {\n    this.clearRange();\n  }\n\n  public clearRange(): void {\n    this.start = this._bufferService.buffer.y;\n    this.end = this._bufferService.buffer.y;\n  }\n\n  public markDirty(y: number): void {\n    if (y < this.start) {\n      this.start = y;\n    } else if (y > this.end) {\n      this.end = y;\n    }\n  }\n\n  public markRangeDirty(y1: number, y2: number): void {\n    if (y1 > y2) {\n      $temp = y1;\n      y1 = y2;\n      y2 = $temp;\n    }\n    if (y1 < this.start) {\n      this.start = y1;\n    }\n    if (y2 > this.end) {\n      this.end = y2;\n    }\n  }\n\n  public markAllDirty(): void {\n    this.markRangeDirty(0, this._bufferService.rows - 1);\n  }\n}\n\nexport function isValidColorIndex(value: number): value is ColorIndex {\n  return 0 <= value && value < 256;\n}\n"
  },
  {
    "path": "src/common/Lifecycle.ts",
    "content": "/**\n * Copyright (c) 2024-2026 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Minimal lifecycle utilities for xterm.js core.\n * Simplified from VS Code's lifecycle.ts - no tracking/leak detection.\n */\n\nexport interface IDisposable {\n  dispose(): void;\n}\n\nexport function toDisposable(fn: () => void): IDisposable {\n  return { dispose: fn };\n}\n\nexport function dispose<T extends IDisposable>(disposable: T): T;\nexport function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;\nexport function dispose<T extends IDisposable>(disposables: T[]): T[];\nexport function dispose<T extends IDisposable>(arg: T | T[] | undefined): T | T[] | undefined {\n  if (!arg) {\n    return arg;\n  }\n  if (Array.isArray(arg)) {\n    for (const d of arg) {\n      d.dispose();\n    }\n    return [];\n  }\n  arg.dispose();\n  return arg;\n}\n\nexport function combinedDisposable(...disposables: IDisposable[]): IDisposable {\n  return toDisposable(() => dispose(disposables));\n}\n\nexport class DisposableStore implements IDisposable {\n  private readonly _disposables = new Set<IDisposable>();\n  private _isDisposed = false;\n\n  public get isDisposed(): boolean {\n    return this._isDisposed;\n  }\n\n  public add<T extends IDisposable>(o: T): T {\n    if (this._isDisposed) {\n      o.dispose();\n    } else {\n      this._disposables.add(o);\n    }\n    return o;\n  }\n\n  public dispose(): void {\n    if (this._isDisposed) {\n      return;\n    }\n    this._isDisposed = true;\n    for (const d of this._disposables) {\n      d.dispose();\n    }\n    this._disposables.clear();\n  }\n\n  public clear(): void {\n    for (const d of this._disposables) {\n      d.dispose();\n    }\n    this._disposables.clear();\n  }\n}\n\nexport abstract class Disposable implements IDisposable {\n  public static readonly None: IDisposable = Object.freeze({ dispose() { } });\n\n  protected readonly _store = new DisposableStore();\n\n  public dispose(): void {\n    this._store.dispose();\n  }\n\n  protected _register<T extends IDisposable>(o: T): T {\n    return this._store.add(o);\n  }\n}\n\nexport class MutableDisposable<T extends IDisposable> implements IDisposable {\n  private _value: T | undefined;\n  private _isDisposed = false;\n\n  public get value(): T | undefined {\n    return this._isDisposed ? undefined : this._value;\n  }\n\n  public set value(value: T | undefined) {\n    if (this._isDisposed || value === this._value) {\n      return;\n    }\n    this._value?.dispose();\n    this._value = value;\n  }\n\n  public clear(): void {\n    this.value = undefined;\n  }\n\n  public dispose(): void {\n    this._isDisposed = true;\n    this._value?.dispose();\n    this._value = undefined;\n  }\n}\n"
  },
  {
    "path": "src/common/MultiKeyMap.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { FourKeyMap, TwoKeyMap } from 'common/MultiKeyMap';\n\nconst strictEqual = assert.strictEqual;\n\ndescribe('TwoKeyMap', () => {\n  let map: TwoKeyMap<number | string, number | string, string>;\n\n  beforeEach(() => {\n    map = new TwoKeyMap();\n  });\n\n  it('set, get', () => {\n    strictEqual(map.get(1, 2), undefined);\n    map.set(1, 2, 'foo');\n    strictEqual(map.get(1, 2), 'foo');\n    map.set(1, 3, 'bar');\n    strictEqual(map.get(1, 2), 'foo');\n    strictEqual(map.get(1, 3), 'bar');\n    map.set(2, 2, 'foo2');\n    map.set(2, 3, 'bar2');\n    strictEqual(map.get(1, 2), 'foo');\n    strictEqual(map.get(1, 3), 'bar');\n    strictEqual(map.get(2, 2), 'foo2');\n    strictEqual(map.get(2, 3), 'bar2');\n  });\n  it('clear', () => {\n    strictEqual(map.get(1, 2), undefined);\n    map.set(1, 2, 'foo');\n    strictEqual(map.get(1, 2), 'foo');\n    map.clear();\n    strictEqual(map.get(1, 2), undefined);\n  });\n});\n\ndescribe('FourKeyMap', () => {\n  let map: FourKeyMap<number | string, number | string, number | string, number | string, string>;\n\n  beforeEach(() => {\n    map = new FourKeyMap();\n  });\n\n  it('set, get', () => {\n    strictEqual(map.get(1, 2, 3, 4), undefined);\n    map.set(1, 2, 3, 4, 'foo');\n    strictEqual(map.get(1, 2, 3, 4), 'foo');\n    map.set(1, 3, 3, 4, 'bar');\n    strictEqual(map.get(1, 2, 3, 4), 'foo');\n    strictEqual(map.get(1, 3, 3, 4), 'bar');\n    map.set(2, 2, 3, 4, 'foo2');\n    map.set(2, 3, 3, 4, 'bar2');\n    strictEqual(map.get(1, 2, 3, 4), 'foo');\n    strictEqual(map.get(1, 3, 3, 4), 'bar');\n    strictEqual(map.get(2, 2, 3, 4), 'foo2');\n    strictEqual(map.get(2, 3, 3, 4), 'bar2');\n  });\n  it('clear', () => {\n    strictEqual(map.get(1, 2, 3, 4), undefined);\n    map.set(1, 2, 3, 4, 'foo');\n    strictEqual(map.get(1, 2, 3, 4), 'foo');\n    map.clear();\n    strictEqual(map.get(1, 2, 3, 4), undefined);\n  });\n});\n"
  },
  {
    "path": "src/common/MultiKeyMap.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport class TwoKeyMap<TFirst extends string | number, TSecond extends string | number, TValue> {\n  private _data: { [bg: string | number]: { [fg: string | number]: TValue | undefined } | undefined } = {};\n\n  public set(first: TFirst, second: TSecond, value: TValue): void {\n    if (!this._data[first]) {\n      this._data[first] = {};\n    }\n    this._data[first as string | number]![second] = value;\n  }\n\n  public get(first: TFirst, second: TSecond): TValue | undefined {\n    return this._data[first as string | number] ? this._data[first as string | number]![second] : undefined;\n  }\n\n  public clear(): void {\n    this._data = {};\n  }\n}\n\nexport class FourKeyMap<TFirst extends string | number, TSecond extends string | number, TThird extends string | number, TFourth extends string | number, TValue> {\n  private _data: TwoKeyMap<TFirst, TSecond, TwoKeyMap<TThird, TFourth, TValue>> = new TwoKeyMap();\n\n  public set(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, value: TValue): void {\n    if (!this._data.get(first, second)) {\n      this._data.set(first, second, new TwoKeyMap());\n    }\n    this._data.get(first, second)!.set(third, fourth, value);\n  }\n\n  public get(first: TFirst, second: TSecond, third: TThird, fourth: TFourth): TValue | undefined {\n    return this._data.get(first, second)?.get(third, fourth);\n  }\n\n  public clear(): void {\n    this._data.clear();\n  }\n}\n"
  },
  {
    "path": "src/common/Platform.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\ninterface INavigator {\n  userAgent: string;\n  language: string;\n  platform: string;\n}\n\n// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but\n// we want this module to live in common.\ndeclare const navigator: INavigator;\ndeclare const process: unknown;\n\n// navigator.userAgent is also checked here because bundling with the process module can cause\n// issues otherwise. Note that navigator exists in Node.js 21+ but the userAgent is\n// \"Node.js/<version>\".\nexport const isNode = (typeof process !== 'undefined' && 'title' in (process as any) && (typeof navigator === 'undefined' || navigator.userAgent.startsWith('Node.js/'))) ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = userAgent.includes('Firefox');\nexport const isChrome = userAgent.includes('Chrome');\nexport const isLegacyEdge = userAgent.includes('Edge');\nexport const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);\n\ninterface IZoomWindow {\n  devicePixelRatio?: number;\n}\n\nexport function getZoomFactor(_targetWindow: IZoomWindow): number {\n  return 1;\n}\nexport function getSafariVersion(): number {\n  if (!isSafari) {\n    return 0;\n  }\n  const majorVersion = userAgent.match(/Version\\/(\\d+)/);\n  if (majorVersion === null || majorVersion.length < 2) {\n    return 0;\n  }\n  return parseInt(majorVersion[1]);\n}\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'].includes(platform);\nexport const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].includes(platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n// Note that when this is true, isLinux will also be true.\nexport const isChromeOS = /\\bCrOS\\b/.test(userAgent);\n"
  },
  {
    "path": "src/common/SortedList.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { SortedList } from 'common/SortedList';\nimport { MockLogService } from 'common/TestUtils.test';\n\nconst deepStrictEqual = assert.deepStrictEqual;\n\ndescribe('SortedList', () => {\n  let list: SortedList<number>;\n  function assertList(expected: number[]): void {\n    deepStrictEqual(Array.from(list.values()), expected);\n  }\n\n  beforeEach(() => {\n    list = new SortedList<number>(e => e, new MockLogService());\n  });\n\n  describe('insert', () => {\n    it('should maintain sorted values', () => {\n      list.insert(10);\n      assertList([10]);\n      list.insert(8);\n      assertList([8, 10]);\n      list.insert(15);\n      assertList([8, 10, 15]);\n      list.insert(2);\n      assertList([2, 8, 10, 15]);\n      list.insert(1);\n      assertList([1, 2, 8, 10, 15]);\n      list.insert(6);\n      assertList([1, 2, 6, 8, 10, 15]);\n    });\n    it('should allow duplicates of the same key', () => {\n      list.insert(5);\n      assertList([5]);\n      list.insert(5);\n      assertList([5, 5]);\n      list.insert(8);\n      assertList([5, 5, 8]);\n      list.insert(5);\n      assertList([5, 5, 5, 8]);\n      list.insert(8);\n      assertList([5, 5, 5, 8, 8]);\n      list.insert(6);\n      assertList([5, 5, 5, 6, 8, 8]);\n    });\n  });\n  it('delete', () => {\n    list.insert(1);\n    list.insert(2);\n    list.insert(4);\n    list.insert(3);\n    list.insert(5);\n    assertList([1, 2, 3, 4, 5]);\n    list.delete(1);\n    assertList([2, 3, 4, 5]);\n    list.delete(3);\n    assertList([2, 4, 5]);\n    list.delete(4);\n    assertList([2, 5]);\n    list.delete(5);\n    assertList([2]);\n    list.delete(2);\n    assertList([]);\n  });\n  it('getKeyIterator', () => {\n    list.insert(5);\n    list.insert(5);\n    list.insert(8);\n    list.insert(5);\n    list.insert(8);\n    list.insert(6);\n    assertList([5, 5, 5, 6, 8, 8]);\n    deepStrictEqual(Array.from(list.getKeyIterator(1)), []);\n    deepStrictEqual(Array.from(list.getKeyIterator(5)), [5, 5, 5]);\n    deepStrictEqual(Array.from(list.getKeyIterator(6)), [6]);\n    deepStrictEqual(Array.from(list.getKeyIterator(8)), [8, 8]);\n    deepStrictEqual(Array.from(list.getKeyIterator(9)), []);\n  });\n  it('clear', () => {\n    list.insert(1);\n    list.insert(2);\n    list.insert(4);\n    list.insert(3);\n    list.insert(5);\n    list.clear();\n    assertList([]);\n  });\n  it('custom key', () => {\n    const customList = new SortedList<{ key: number }>(e => e.key, new MockLogService());\n    customList.insert({ key: 5 });\n    customList.insert({ key: 2 });\n    customList.insert({ key: 10 });\n    customList.insert({ key: 5 });\n    customList.insert({ key: 6 });\n    deepStrictEqual(Array.from(customList.values()), [\n      { key: 2 },\n      { key: 5 },\n      { key: 5 },\n      { key: 6 },\n      { key: 10 }\n    ]);\n  });\n  describe('values', () => {\n    it('should iterate correctly when list items change during iteration', () => {\n      list.insert(1);\n      list.insert(2);\n      list.insert(3);\n      list.insert(4);\n      const visited: number[] = [];\n      for (const item of list.values()) {\n        visited.push(item);\n        list.delete(item);\n      }\n      deepStrictEqual(visited, [1, 2, 3, 4]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/SortedList.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IdleTaskQueue } from 'common/TaskQueue';\nimport type { ILogService } from 'common/services/Services';\n\n// Work variables to avoid garbage collection.\nlet i = 0;\n\n/**\n * A generic list that is maintained in sorted order and allows values with duplicate keys. Deferred\n * batch insertion and deletion is used to significantly reduce the time it takes to insert and\n * delete a large amount of items in succession. This list is based on binary search and as such\n * locating a key will take O(log n) amortized, this includes the by key iterator.\n */\nexport class SortedList<T> {\n  private _array: T[] = [];\n\n  private readonly _insertedValues: T[] = [];\n  private readonly _flushInsertedTask: InstanceType<typeof IdleTaskQueue>;\n  private _isFlushingInserted = false;\n\n  private readonly _deletedIndices: number[] = [];\n  private readonly _flushDeletedTask: InstanceType<typeof IdleTaskQueue>;\n  private _isFlushingDeleted = false;\n\n  constructor(\n    private readonly _getKey: (value: T) => number,\n    logService: ILogService\n  ) {\n    this._flushInsertedTask = new IdleTaskQueue(logService);\n    this._flushDeletedTask = new IdleTaskQueue(logService);\n  }\n\n  public clear(): void {\n    this._array.length = 0;\n    this._insertedValues.length = 0;\n    this._flushInsertedTask.clear();\n    this._isFlushingInserted = false;\n    this._deletedIndices.length = 0;\n    this._flushDeletedTask.clear();\n    this._isFlushingDeleted = false;\n  }\n\n  public insert(value: T): void {\n    this._flushCleanupDeleted();\n    if (this._insertedValues.length === 0) {\n      this._flushInsertedTask.enqueue(() => this._flushInserted());\n    }\n    this._insertedValues.push(value);\n  }\n\n  private _flushInserted(): void {\n    const sortedAddedValues = this._insertedValues.sort((a, b) => this._getKey(a) - this._getKey(b));\n    let sortedAddedValuesIndex = 0;\n    let arrayIndex = 0;\n\n    const newArray = new Array(this._array.length + this._insertedValues.length);\n\n    for (let newArrayIndex = 0; newArrayIndex < newArray.length; newArrayIndex++) {\n      if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) <= this._getKey(this._array[arrayIndex])) {\n        newArray[newArrayIndex] = sortedAddedValues[sortedAddedValuesIndex];\n        sortedAddedValuesIndex++;\n      } else {\n        newArray[newArrayIndex] = this._array[arrayIndex++];\n      }\n    }\n\n    this._array = newArray;\n    this._insertedValues.length = 0;\n  }\n\n  private _flushCleanupInserted(): void {\n    if (!this._isFlushingInserted && this._insertedValues.length > 0) {\n      this._flushInsertedTask.flush();\n    }\n  }\n\n  public delete(value: T): boolean {\n    this._flushCleanupInserted();\n    if (this._array.length === 0) {\n      return false;\n    }\n    const key = this._getKey(value);\n    if (key === undefined) {\n      return false;\n    }\n    i = this._search(key);\n    if (i === -1) {\n      return false;\n    }\n    if (this._getKey(this._array[i]) !== key) {\n      return false;\n    }\n    do {\n      if (this._array[i] === value) {\n        if (this._deletedIndices.length === 0) {\n          this._flushDeletedTask.enqueue(() => this._flushDeleted());\n        }\n        this._deletedIndices.push(i);\n        return true;\n      }\n    } while (++i < this._array.length && this._getKey(this._array[i]) === key);\n    return false;\n  }\n\n  private _flushDeleted(): void {\n    this._isFlushingDeleted = true;\n    const sortedDeletedIndices = this._deletedIndices.sort((a, b) => a - b);\n    let sortedDeletedIndicesIndex = 0;\n    const newArray = new Array(this._array.length - sortedDeletedIndices.length);\n    let newArrayIndex = 0;\n    for (let i = 0; i < this._array.length; i++) {\n      if (sortedDeletedIndices[sortedDeletedIndicesIndex] === i) {\n        sortedDeletedIndicesIndex++;\n      } else {\n        newArray[newArrayIndex++] = this._array[i];\n      }\n    }\n    this._array = newArray;\n    this._deletedIndices.length = 0;\n    this._isFlushingDeleted = false;\n  }\n\n  private _flushCleanupDeleted(): void {\n    if (!this._isFlushingDeleted && this._deletedIndices.length > 0) {\n      this._flushDeletedTask.flush();\n    }\n  }\n\n  public *getKeyIterator(key: number): IterableIterator<T> {\n    this._flushCleanupInserted();\n    this._flushCleanupDeleted();\n    if (this._array.length === 0) {\n      return;\n    }\n    i = this._search(key);\n    if (i < 0 || i >= this._array.length) {\n      return;\n    }\n    if (this._getKey(this._array[i]) !== key) {\n      return;\n    }\n    do {\n      yield this._array[i];\n    } while (++i < this._array.length && this._getKey(this._array[i]) === key);\n  }\n\n  public forEachByKey(key: number, callback: (value: T) => void): void {\n    this._flushCleanupInserted();\n    this._flushCleanupDeleted();\n    if (this._array.length === 0) {\n      return;\n    }\n    i = this._search(key);\n    if (i < 0 || i >= this._array.length) {\n      return;\n    }\n    if (this._getKey(this._array[i]) !== key) {\n      return;\n    }\n    do {\n      callback(this._array[i]);\n    } while (++i < this._array.length && this._getKey(this._array[i]) === key);\n  }\n\n  public values(): IterableIterator<T> {\n    this._flushCleanupInserted();\n    this._flushCleanupDeleted();\n    // Duplicate the array to avoid issues when _array changes while iterating\n    return [...this._array].values();\n  }\n\n  private _search(key: number): number {\n    let min = 0;\n    let max = this._array.length - 1;\n    while (max >= min) {\n      let mid = (min + max) >> 1;\n      const midKey = this._getKey(this._array[mid]);\n      if (midKey > key) {\n        max = mid - 1;\n      } else if (midKey < key) {\n        min = mid + 1;\n      } else {\n        // key in list, walk to lowest duplicate\n        while (mid > 0 && this._getKey(this._array[mid - 1]) === key) {\n          mid--;\n        }\n        return mid;\n      }\n    }\n    // key not in list\n    // still return closest min (also used as insert position)\n    return min;\n  }\n}\n"
  },
  {
    "path": "src/common/TaskQueue.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { isNode } from 'common/Platform';\nimport type { ILogService } from 'common/services/Services';\n\ninterface ITaskQueue {\n  /**\n   * Adds a task to the queue which will run in a future idle callback.\n   * To avoid perceivable stalls on the mainthread, tasks with heavy workload\n   * should split their work into smaller pieces and return `true` to get\n   * called again until the work is done (on falsy return value).\n   */\n  enqueue(task: () => boolean | void): void;\n\n  /**\n   * Flushes the queue, running all remaining tasks synchronously.\n   */\n  flush(): void;\n\n  /**\n   * Clears any remaining tasks from the queue, these will not be run.\n   */\n  clear(): void;\n}\n\ninterface ITaskDeadline {\n  timeRemaining(): number;\n}\ntype CallbackWithDeadline = (deadline: ITaskDeadline) => void;\n\nabstract class TaskQueue implements ITaskQueue {\n  private _tasks: (() => boolean | void)[] = [];\n  private _idleCallback?: number;\n  private _i = 0;\n  protected readonly _logService: ILogService;\n\n  constructor(logService: ILogService) {\n    this._logService = logService;\n  }\n\n  protected abstract _requestCallback(callback: CallbackWithDeadline): number;\n  protected abstract _cancelCallback(identifier: number): void;\n\n  public enqueue(task: () => boolean | void): void {\n    this._tasks.push(task);\n    this._start();\n  }\n\n  public flush(): void {\n    while (this._i < this._tasks.length) {\n      if (!this._tasks[this._i]()) {\n        this._i++;\n      }\n    }\n    this.clear();\n  }\n\n  public clear(): void {\n    if (this._idleCallback) {\n      this._cancelCallback(this._idleCallback);\n      this._idleCallback = undefined;\n    }\n    this._i = 0;\n    this._tasks.length = 0;\n  }\n\n  private _start(): void {\n    if (!this._idleCallback) {\n      this._idleCallback = this._requestCallback(this._process.bind(this));\n    }\n  }\n\n  private _process(deadline: ITaskDeadline): void {\n    this._idleCallback = undefined;\n    let taskDuration = 0;\n    let longestTask = 0;\n    let lastDeadlineRemaining = deadline.timeRemaining();\n    let deadlineRemaining = 0;\n    while (this._i < this._tasks.length) {\n      taskDuration = performance.now();\n      if (!this._tasks[this._i]()) {\n        this._i++;\n      }\n      // other than performance.now, performance.now might not be stable (changes on wall clock\n      // changes), this is not an issue here as a clock change during a short running task is very\n      // unlikely in case it still happened and leads to negative duration, simply assume 1 msec\n      taskDuration = Math.max(1, performance.now() - taskDuration);\n      longestTask = Math.max(taskDuration, longestTask);\n      // Guess the following task will take a similar time to the longest task in this batch, allow\n      // additional room to try avoid exceeding the deadline\n      deadlineRemaining = deadline.timeRemaining();\n      if (longestTask * 1.5 > deadlineRemaining) {\n        // Warn when the time exceeding the deadline is over 20ms, if this happens in practice the\n        // task should be split into sub-tasks to ensure the UI remains responsive.\n        if (lastDeadlineRemaining - taskDuration < -20) {\n          this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(lastDeadlineRemaining - taskDuration))}ms`);\n        }\n        this._start();\n        return;\n      }\n      lastDeadlineRemaining = deadlineRemaining;\n    }\n    this.clear();\n  }\n}\n\n/**\n * A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames\n * per second. The tasks will run in the order they are enqueued, but they will run some time later,\n * and care should be taken to ensure they're non-urgent and will not introduce race conditions.\n */\nexport class PriorityTaskQueue extends TaskQueue {\n  protected _requestCallback(callback: CallbackWithDeadline): number {\n    return setTimeout(() => callback(this._createDeadline(16)));\n  }\n\n  protected _cancelCallback(identifier: number): void {\n    clearTimeout(identifier);\n  }\n\n  private _createDeadline(duration: number): ITaskDeadline {\n    const end = performance.now() + duration;\n    return {\n      timeRemaining: () => Math.max(0, end - performance.now())\n    };\n  }\n}\n\nclass IdleTaskQueueInternal extends TaskQueue {\n  protected _requestCallback(callback: IdleRequestCallback): number {\n    return requestIdleCallback(callback);\n  }\n\n  protected _cancelCallback(identifier: number): void {\n    cancelIdleCallback(identifier);\n  }\n}\n\n/**\n * A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's\n * deadline given by the environment. The tasks will run in the order they are enqueued, but they\n * will run some time later, and care should be taken to ensure they're non-urgent and will not\n * introduce race conditions.\n *\n * This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? IdleTaskQueueInternal : PriorityTaskQueue;\n\n/**\n * An object that tracks a single debounced task that will run on the next idle frame. When called\n * multiple times, only the last set task will run.\n */\nexport class DebouncedIdleTask {\n  private _queue: ITaskQueue;\n\n  constructor(logService: ILogService) {\n    this._queue = new IdleTaskQueue(logService);\n  }\n\n  public set(task: () => boolean | void): void {\n    this._queue.clear();\n    this._queue.enqueue(task);\n  }\n\n  public flush(): void {\n    this._queue.flush();\n  }\n}\n"
  },
  {
    "path": "src/common/TestUtils.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IMouseStateService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService, type IBufferResizeEvent } from 'common/services/Services';\nimport { UnicodeService } from 'common/services/UnicodeService';\nimport { clone } from 'common/Clone';\nimport { DEFAULT_OPTIONS } from 'common/services/OptionsService';\nimport { IBufferSet, IBuffer } from 'common/buffer/Types';\nimport { BufferSet } from 'common/buffer/BufferSet';\nimport { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable } from 'common/Types';\nimport { UnicodeV6 } from 'common/input/UnicodeV6';\nimport { IDecorationOptions, IDecoration } from '@xterm/xterm';\nimport { Emitter, type IEvent } from 'common/Event';\nimport { CellData } from 'common/buffer/CellData';\nimport { DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH } from 'common/buffer/Constants';\n\nexport function createCellData(attr: number, char: string, width: number): CellData {\n  return CellData.fromCharData([attr, char, width, char.length === 0 ? 0 : char.charCodeAt(0)]);\n}\n\nexport const NULL_CELL_DATA = Object.freeze(createCellData(DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH));\n\nexport class MockBufferService implements IBufferService {\n  public serviceBrand: any;\n  public get buffer(): IBuffer { return this.buffers.active; }\n  public buffers: IBufferSet = {} as any;\n  public onResize: IEvent<IBufferResizeEvent> = new Emitter<IBufferResizeEvent>().event;\n  public onScroll: IEvent<number> = new Emitter<number>().event;\n  private readonly _onScroll = new Emitter<number>();\n  public isUserScrolling: boolean = false;\n  constructor(\n    public cols: number,\n    public rows: number,\n    optionsService: IOptionsService = new MockOptionsService()\n  ) {\n    this.buffers = new BufferSet(optionsService, this, new MockLogService());\n    // Listen to buffer activation events and automatically fire scroll events\n    this.buffers.onBufferActivate(e => {\n      this._onScroll.fire(e.activeBuffer.ydisp);\n    });\n  }\n  public scrollPages(pageCount: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToTop(): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToLine(line: number): void {\n    throw new Error('Method not implemented.');\n  }\n  public scroll(eraseAttr: IAttributeData, isWrapped: boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollToBottom(): void {\n    throw new Error('Method not implemented.');\n  }\n  public scrollLines(disp: number, suppressScrollEvent?: boolean): void {\n    throw new Error('Method not implemented.');\n  }\n  public resize(cols: number, rows: number): void {\n    this.cols = cols;\n    this.rows = rows;\n  }\n  public reset(): void { }\n}\n\nexport class MockMouseStateService implements IMouseStateService {\n  public serviceBrand: any;\n  public areMouseEventsActive: boolean = false;\n  public activeEncoding: string = '';\n  public activeProtocol: string = '';\n  public isDefaultEncoding: boolean = true;\n  public isPixelEncoding: boolean = false;\n  public addEncoding(name: string): void { }\n  public addProtocol(name: string): void { }\n  public reset(): void { }\n  public onProtocolChange: IEvent<CoreMouseEventType> = new Emitter<CoreMouseEventType>().event;\n  public restrictMouseEvent(event: ICoreMouseEvent): boolean { return true; }\n  public encodeMouseEvent(event: ICoreMouseEvent): string { return ''; }\n  public setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void { }\n  public allowCustomWheelEvent(ev: WheelEvent): boolean { return true; }\n}\n\nexport class MockCharsetService implements ICharsetService {\n  public serviceBrand: any;\n  public charset: ICharset | undefined;\n  public glevel: number = 0;\n  public charsets: (ICharset | undefined)[] = [];\n  public reset(): void { }\n  public setgLevel(g: number): void {\n    this.glevel = g;\n    this.charset = this.charsets[g];\n  }\n  public setgCharset(g: number, charset: ICharset | undefined): void {\n    this.charsets[g] = charset;\n    if (this.glevel === g) {\n      this.charset = charset;\n    }\n  }\n}\n\nexport class MockCoreService implements ICoreService {\n  public serviceBrand: any;\n  public isCursorInitialized: boolean = true;\n  public isCursorHidden: boolean = false;\n  public isFocused: boolean = false;\n  public modes: IModes = {\n    insertMode: false\n  };\n  public decPrivateModes: IDecPrivateModes = {\n    applicationCursorKeys: false,\n    applicationKeypad: false,\n    bracketedPasteMode: false,\n    colorSchemeUpdates: false,\n    cursorBlink: undefined,\n    cursorStyle: undefined,\n    origin: false,\n    reverseWraparound: false,\n    sendFocus: false,\n    synchronizedOutput: false,\n    win32InputMode: false,\n    wraparound: true\n  };\n  public kittyKeyboard = {\n    flags: 0,\n    mainFlags: 0,\n    altFlags: 0,\n    mainStack: [] as number[],\n    altStack: [] as number[]\n  };\n  public onData: IEvent<string> = new Emitter<string>().event;\n  public onUserInput: IEvent<void> = new Emitter<void>().event;\n  public onBinary: IEvent<string> = new Emitter<string>().event;\n  public onRequestScrollToBottom: IEvent<void> = new Emitter<void>().event;\n  public reset(): void { }\n  public triggerDataEvent(data: string, wasUserInput?: boolean): void { }\n  public triggerBinaryEvent(data: string): void { }\n}\n\nexport class MockLogService implements ILogService {\n  public serviceBrand: any;\n  public logLevel = LogLevelEnum.DEBUG;\n  public trace(message: any, ...optionalParams: any[]): void { }\n  public debug(message: any, ...optionalParams: any[]): void { }\n  public info(message: any, ...optionalParams: any[]): void { }\n  public warn(message: any, ...optionalParams: any[]): void { }\n  public error(message: any, ...optionalParams: any[]): void { }\n}\n\nexport class MockOptionsService implements IOptionsService {\n  public serviceBrand: any;\n  public readonly rawOptions: Required<ITerminalOptions> = clone(DEFAULT_OPTIONS);\n  public options: Required<ITerminalOptions> = this.rawOptions;\n  public onOptionChange: IEvent<keyof ITerminalOptions> = new Emitter<keyof ITerminalOptions>().event;\n  constructor(testOptions?: Partial<ITerminalOptions>) {\n    if (testOptions) {\n      for (const key of Object.keys(testOptions)) {\n        this.rawOptions[key] = testOptions[key];\n      }\n    }\n  }\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  public onSpecificOptionChange<T extends keyof ITerminalOptions>(key: T, listener: (arg1: ITerminalOptions[T]) => any): IDisposable {\n    return this.onOptionChange(eventKey => {\n      if (eventKey === key) {\n        listener(this.rawOptions[key]);\n      }\n    });\n  }\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable {\n    return this.onOptionChange(eventKey => {\n      if (keys.indexOf(eventKey) !== -1) {\n        listener();\n      }\n    });\n  }\n  public setOptions(options: ITerminalOptions): void {\n    for (const key of Object.keys(options)) {\n      this.options[key] = options[key];\n    }\n  }\n}\n\nexport class MockOscLinkService implements IOscLinkService {\n  public serviceBrand: any;\n  public registerLink(linkData: IOscLinkData): number {\n    return 1;\n  }\n  public getLinkData(linkId: number): IOscLinkData | undefined {\n    return undefined;\n  }\n  public addLineToLink(linkId: number, y: number): void {\n  }\n}\n\n// defaults to V6 always to keep tests passing\nexport class MockUnicodeService implements IUnicodeService {\n  public serviceBrand: any;\n  private _provider = new UnicodeV6();\n  public register(provider: IUnicodeVersionProvider): void {\n    throw new Error('Method not implemented.');\n  }\n  public versions: string[] = [];\n  public activeVersion: string = '';\n  public onChange: IEvent<string> = new Emitter<string>().event;\n  public wcwidth = (codepoint: number): UnicodeCharWidth => this._provider.wcwidth(codepoint);\n  public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {\n    let width = this.wcwidth(codepoint);\n    let shouldJoin = width === 0 && preceding !== 0;\n    if (shouldJoin) {\n      const oldWidth = UnicodeService.extractWidth(preceding);\n      if (oldWidth === 0) {\n        shouldJoin = false;\n      } else if (oldWidth > width) {\n        width = oldWidth;\n      }\n    }\n    return UnicodeService.createPropertyValue(0, width, shouldJoin);\n  }\n  public getStringCellWidth(s: string): number {\n    throw new Error('Method not implemented.');\n  }\n}\n\nexport class MockDecorationService implements IDecorationService {\n  public serviceBrand: any;\n  public get decorations(): IterableIterator<IInternalDecoration> { return [].values(); }\n  public onDecorationRegistered = new Emitter<IInternalDecoration>().event;\n  public onDecorationRemoved = new Emitter<IInternalDecoration>().event;\n  public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; }\n  public reset(): void { }\n  public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { }\n  public dispose(): void { }\n}\n"
  },
  {
    "path": "src/common/Types.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDeleteEvent, IInsertEvent } from 'common/CircularList';\nimport { UnderlineStyle } from 'common/buffer/Constants';\nimport { IBufferSet } from 'common/buffer/Types';\nimport { IParams } from 'common/parser/Types';\nimport { IMouseStateService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';\nimport { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm';\nimport type { Emitter, IEvent } from 'common/Event';\n\nexport interface ICoreTerminal {\n  mouseStateService: IMouseStateService;\n  coreService: ICoreService;\n  optionsService: IOptionsService;\n  unicodeService: IUnicodeService;\n  buffers: IBufferSet;\n  options: Required<ITerminalOptions>;\n  registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;\n  registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;\n  registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;\n  registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;\n  registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;\n}\n\nexport interface IDisposable {\n  dispose(): void;\n}\n\n// TODO: The options that are not in the public API should be reviewed\nexport interface ITerminalOptions extends IPublicTerminalOptions {\n  [key: string]: any;\n  convertEol?: boolean;\n  termName?: string;\n}\n\nexport type CursorStyle = 'block' | 'underline' | 'bar';\n\nexport type CursorInactiveStyle = 'outline' | 'block' | 'bar' | 'underline' | 'none';\n\nexport type XtermListener = (...args: any[]) => void;\n\n/**\n * A keyboard event interface which does not depend on the DOM, KeyboardEvent implicitly extends\n * this event.\n */\nexport interface IKeyboardEvent {\n  altKey: boolean;\n  ctrlKey: boolean;\n  shiftKey: boolean;\n  metaKey: boolean;\n  /** @deprecated See KeyboardEvent.keyCode */\n  keyCode: number;\n  key: string;\n  type: string;\n  code: string;\n}\n\nexport interface IScrollEvent {\n  position: number;\n}\n\nexport interface ICircularList<T> {\n  length: number;\n  maxLength: number;\n  isFull: boolean;\n\n  onDeleteEmitter: Emitter<IDeleteEvent>;\n  onDelete: IEvent<IDeleteEvent>;\n  onInsertEmitter: Emitter<IInsertEvent>;\n  onInsert: IEvent<IInsertEvent>;\n  onTrimEmitter: Emitter<number>;\n  onTrim: IEvent<number>;\n\n  get(index: number): T | undefined;\n  set(index: number, value: T): void;\n  push(value: T): void;\n  recycle(): T;\n  pop(): T | undefined;\n  splice(start: number, deleteCount: number, ...items: T[]): void;\n  trimStart(count: number): void;\n  shiftElements(start: number, count: number, offset: number): void;\n}\n\nexport const enum KeyboardResultType {\n  SEND_KEY,\n  SELECT_ALL,\n  PAGE_UP,\n  PAGE_DOWN\n}\n\nexport interface IKeyboardResult {\n  type: KeyboardResultType;\n  cancel: boolean;\n  key: string | undefined;\n}\n\nexport interface ICharset {\n  [key: string]: string | undefined;\n}\n\nexport type CharData = [attr: number, char: string, width: number, code: number];\n\nexport interface IColor {\n  readonly css: string;\n  readonly rgba: number; // 32-bit int with rgba in each byte\n}\nexport type IColorRGB = [red: number, green: number, blue: number];\n\nexport interface IExtendedAttrs {\n  ext: number;\n  underlineStyle: UnderlineStyle;\n  underlineColor: number;\n  underlineVariantOffset: number;\n  urlId: number;\n  clone(): IExtendedAttrs;\n  isEmpty(): boolean;\n}\n\n/**\n * Tracks the current hyperlink. Since these are treated as extended attirbutes, these get passed on\n * to the linkifier when anything is printed. Doing it this way ensures that even when the cursor\n * moves around unexpectedly the link is tracked, as opposed to using a start position and\n * finalizing it at the end.\n */\nexport interface IOscLinkData {\n  id?: string;\n  uri: string;\n}\n\n/**\n * An object that represents all attributes of a cell.\n */\nexport interface IAttributeData {\n  /**\n   * \"fg\" is a 32-bit unsigned integer that stores the foreground color of the cell in the 24 least\n   * significant bits and additional flags in the remaining 8 bits.\n   */\n  fg: number;\n  /**\n   * \"bg\" is a 32-bit unsigned integer that stores the background color of the cell in the 24 least\n   * significant bits and additional flags in the remaining 8 bits.\n   */\n  bg: number;\n  /**\n   * \"extended\", aka \"ext\", stores extended attributes beyond those available in fg and bg. This\n   * data is optional on a cell and encodes less common data.\n   */\n  extended: IExtendedAttrs;\n\n  clone(): IAttributeData;\n\n  // flags\n  isInverse(): number;\n  isBold(): number;\n  isUnderline(): number;\n  isBlink(): number;\n  isInvisible(): number;\n  isItalic(): number;\n  isDim(): number;\n  isStrikethrough(): number;\n  isProtected(): number;\n  isOverline(): number;\n\n  /**\n   * The color mode of the foreground color which determines how to decode {@link getFgColor},\n   * possible values include {@link Attributes.CM_DEFAULT}, {@link Attributes.CM_P16},\n   * {@link Attributes.CM_P256} and {@link Attributes.CM_RGB}.\n   */\n  getFgColorMode(): number;\n  /**\n   * The color mode of the background color which determines how to decode {@link getBgColor},\n   * possible values include {@link Attributes.CM_DEFAULT}, {@link Attributes.CM_P16},\n   * {@link Attributes.CM_P256} and {@link Attributes.CM_RGB}.\n   */\n  getBgColorMode(): number;\n  isFgRGB(): boolean;\n  isBgRGB(): boolean;\n  isFgPalette(): boolean;\n  isBgPalette(): boolean;\n  isFgDefault(): boolean;\n  isBgDefault(): boolean;\n  isAttributeDefault(): boolean;\n\n  /**\n   * Gets an integer representation of the foreground color, how to decode the color depends on the\n   * color mode {@link getFgColorMode}.\n   */\n  getFgColor(): number;\n  /**\n   * Gets an integer representation of the background color, how to decode the color depends on the\n   * color mode {@link getBgColorMode}.\n   */\n  getBgColor(): number;\n\n  // extended attrs\n  hasExtendedAttrs(): number;\n  updateExtended(): void;\n  getUnderlineColor(): number;\n  getUnderlineColorMode(): number;\n  isUnderlineColorRGB(): boolean;\n  isUnderlineColorPalette(): boolean;\n  isUnderlineColorDefault(): boolean;\n  getUnderlineStyle(): number;\n  getUnderlineVariantOffset(): number;\n}\n\n/** Cell data */\nexport interface ICellData extends IAttributeData {\n  content: number;\n  combinedData: string;\n  isCombined(): number;\n  getWidth(): number;\n  getChars(): string;\n  getCode(): number;\n  setFromCharData(value: CharData): void;\n  getAsCharData(): CharData;\n}\n\n/**\n * Interface for a line in the terminal buffer.\n */\nexport interface IBufferLine {\n  length: number;\n  isWrapped: boolean;\n  get(index: number): CharData;\n  set(index: number, value: CharData): void;\n  loadCell(index: number, cell: ICellData): ICellData;\n  setCell(index: number, cell: ICellData): void;\n  setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void;\n  addCodepointToCell(index: number, codePoint: number, width: number): void;\n  insertCells(pos: number, n: number, ch: ICellData): void;\n  deleteCells(pos: number, n: number, fill: ICellData): void;\n  replaceCells(start: number, end: number, fill: ICellData, respectProtect?: boolean): void;\n  resize(cols: number, fill: ICellData): boolean;\n  cleanupMemory(): number;\n  fill(fillCellData: ICellData, respectProtect?: boolean): void;\n  copyFrom(line: IBufferLine): void;\n  clone(): IBufferLine;\n  getTrimmedLength(): number;\n  getNoBgTrimmedLength(): number;\n  translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;\n\n  /* direct access to cell attrs */\n  getWidth(index: number): number;\n  hasWidth(index: number): number;\n  getFg(index: number): number;\n  getBg(index: number): number;\n  hasContent(index: number): number;\n  getCodePoint(index: number): number;\n  isCombined(index: number): number;\n  getString(index: number): string;\n}\n\nexport interface IMarker extends IDisposable {\n  readonly id: number;\n  readonly isDisposed: boolean;\n  readonly line: number;\n  onDispose: IEvent<void>;\n}\nexport interface IModes {\n  insertMode: boolean;\n}\n\nexport interface IDecPrivateModes {\n  applicationCursorKeys: boolean;\n  applicationKeypad: boolean;\n  bracketedPasteMode: boolean;\n  colorSchemeUpdates: boolean;\n  cursorBlink: boolean | undefined;\n  cursorStyle: CursorStyle | undefined;\n  origin: boolean;\n  reverseWraparound: boolean;\n  sendFocus: boolean;\n  synchronizedOutput: boolean;\n  win32InputMode: boolean;\n  wraparound: boolean; // defaults: xterm - true, vt100 - false\n}\n\n/**\n * Kitty keyboard protocol state.\n * Maintains per-screen stacks of enhancement flags.\n */\nexport interface IKittyKeyboardState {\n  /** Current active enhancement flags (for current screen) */\n  flags: number;\n  /** Saved flags for main screen when alt is active */\n  mainFlags: number;\n  /** Saved flags for alternate screen when main is active */\n  altFlags: number;\n  /** Stack of flags for main screen */\n  mainStack: number[];\n  /** Stack of flags for alternate screen */\n  altStack: number[];\n}\n\nexport interface IRowRange {\n  start: number;\n  end: number;\n}\n\n/**\n * Interface for mouse events in the core.\n */\nexport const enum CoreMouseButton {\n  LEFT = 0,\n  MIDDLE = 1,\n  RIGHT = 2,\n  NONE = 3,\n  WHEEL = 4,\n  // additional buttons 1..8\n  // untested!\n  AUX1 = 8,\n  AUX2 = 9,\n  AUX3 = 10,\n  AUX4 = 11,\n  AUX5 = 12,\n  AUX6 = 13,\n  AUX7 = 14,\n  AUX8 = 15\n}\n\nexport const enum CoreMouseAction {\n  UP = 0,     // buttons, wheel\n  DOWN = 1,   // buttons, wheel\n  LEFT = 2,   // wheel only\n  RIGHT = 3,  // wheel only\n  MOVE = 32   // buttons only\n}\n\nexport interface ICoreMouseEvent {\n  /** column (zero based). */\n  col: number;\n  /** row (zero based). */\n  row: number;\n  /** xy pixel positions. */\n  x: number;\n  y: number;\n  /**\n   * Button the action occured. Due to restrictions of the tracking protocols\n   * it is not possible to report multiple buttons at once.\n   * Wheel is treated as a button.\n   * There are invalid combinations of buttons and actions possible\n   * (like move + wheel), those are silently ignored by the MouseStateService.\n   */\n  button: CoreMouseButton;\n  action: CoreMouseAction;\n  /**\n   * Modifier states.\n   * Protocols will add/ignore those based on specific restrictions.\n   */\n  ctrl?: boolean;\n  alt?: boolean;\n  shift?: boolean;\n}\n\n/**\n * CoreMouseEventType\n * To be reported to the browser component which events a mouse\n * protocol wants to be catched and forwarded as an ICoreMouseEvent\n * to MouseStateService.\n */\nexport const enum CoreMouseEventType {\n  NONE = 0,\n  /** any mousedown event */\n  DOWN = 1,\n  /** any mouseup event */\n  UP = 2,\n  /** any mousemove event while a button is held */\n  DRAG = 4,\n  /** any mousemove event without a button */\n  MOVE = 8,\n  /** any wheel event */\n  WHEEL = 16\n}\n\n/**\n * Mouse protocol interface.\n * A mouse protocol can be registered and activated at the MouseStateService.\n * `events` should contain a list of needed events as a hint for the browser component\n * to install/remove the appropriate event handlers.\n * `restrict` applies further protocol specific restrictions like not allowed\n * modifiers or filtering invalid event types.\n */\nexport interface ICoreMouseProtocol {\n  events: CoreMouseEventType;\n  restrict: (e: ICoreMouseEvent) => boolean;\n}\n\n/**\n * CoreMouseEncoding\n * The tracking encoding can be registered and activated at the MouseStateService.\n * If a ICoreMouseEvent passes all procotol restrictions it will be encoded\n * with the active encoding and sent out.\n * Note: Returning an empty string will supress sending a mouse report,\n * which can be used to skip creating falsey reports in limited encodings\n * (DEFAULT only supports up to 223 1-based as coord value).\n */\nexport type CoreMouseEncoding = (event: ICoreMouseEvent) => string;\n\n/**\n * windowOptions\n */\nexport interface IWindowOptions {\n  restoreWin?: boolean;\n  minimizeWin?: boolean;\n  setWinPosition?: boolean;\n  setWinSizePixels?: boolean;\n  raiseWin?: boolean;\n  lowerWin?: boolean;\n  refreshWin?: boolean;\n  setWinSizeChars?: boolean;\n  maximizeWin?: boolean;\n  fullscreenWin?: boolean;\n  getWinState?: boolean;\n  getWinPosition?: boolean;\n  getWinSizePixels?: boolean;\n  getScreenSizePixels?: boolean;\n  getCellSizePixels?: boolean;\n  getWinSizeChars?: boolean;\n  getScreenSizeChars?: boolean;\n  getIconTitle?: boolean;\n  getWinTitle?: boolean;\n  pushTitle?: boolean;\n  popTitle?: boolean;\n  setWinLines?: boolean;\n}\n\n// color events from common, used for OSC 4/10/11/12 and 104/110/111/112\nexport const enum ColorRequestType {\n  REPORT = 0,\n  SET = 1,\n  RESTORE = 2\n}\n\n// IntRange from https://stackoverflow.com/a/39495173\ntype Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N\n  ? Acc[number]\n  : Enumerate<N, [...Acc, Acc['length']]>;\ntype IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;\n\nexport type ColorIndex = IntRange<0, 256>; // number from 0 to 255\nexport type AllColorIndex = ColorIndex | SpecialColorIndex;\nexport const enum SpecialColorIndex {\n  FOREGROUND = 256,\n  BACKGROUND = 257,\n  CURSOR = 258\n}\nexport interface IColorReportRequest {\n  type: ColorRequestType.REPORT;\n  index: AllColorIndex;\n}\nexport interface IColorSetRequest {\n  type: ColorRequestType.SET;\n  index: AllColorIndex;\n  color: IColorRGB;\n}\nexport interface IColorRestoreRequest {\n  type: ColorRequestType.RESTORE;\n  index?: AllColorIndex;\n}\nexport type IColorEvent = (IColorReportRequest | IColorSetRequest | IColorRestoreRequest)[];\n\n\n/**\n * Calls the parser and handles actions generated by the parser.\n */\nexport interface IInputHandler {\n  onTitleChange: IEvent<string>;\n\n  parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise<boolean>;\n  print(data: Uint32Array, start: number, end: number): void;\n  registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;\n  registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;\n  registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;\n  registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;\n  registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;\n\n  /** C0 BEL */ bell(): boolean;\n  /** C0 LF */ lineFeed(): boolean;\n  /** C0 CR */ carriageReturn(): boolean;\n  /** C0 BS */ backspace(): boolean;\n  /** C0 HT */ tab(): boolean;\n  /** C0 SO */ shiftOut(): boolean;\n  /** C0 SI */ shiftIn(): boolean;\n\n  /** CSI @ */ insertChars(params: IParams): boolean;\n  /** CSI SP @ */ scrollLeft(params: IParams): boolean;\n  /** CSI A */ cursorUp(params: IParams): boolean;\n  /** CSI SP A */ scrollRight(params: IParams): boolean;\n  /** CSI B */ cursorDown(params: IParams): boolean;\n  /** CSI C */ cursorForward(params: IParams): boolean;\n  /** CSI D */ cursorBackward(params: IParams): boolean;\n  /** CSI E */ cursorNextLine(params: IParams): boolean;\n  /** CSI F */ cursorPrecedingLine(params: IParams): boolean;\n  /** CSI G */ cursorCharAbsolute(params: IParams): boolean;\n  /** CSI H */ cursorPosition(params: IParams): boolean;\n  /** CSI I */ cursorForwardTab(params: IParams): boolean;\n  /** CSI J */ eraseInDisplay(params: IParams): boolean;\n  /** CSI K */ eraseInLine(params: IParams): boolean;\n  /** CSI L */ insertLines(params: IParams): boolean;\n  /** CSI M */ deleteLines(params: IParams): boolean;\n  /** CSI P */ deleteChars(params: IParams): boolean;\n  /** CSI S */ scrollUp(params: IParams): boolean;\n  /** CSI T */ scrollDown(params: IParams, collect?: string): boolean;\n  /** CSI X */ eraseChars(params: IParams): boolean;\n  /** CSI Z */ cursorBackwardTab(params: IParams): boolean;\n  /** CSI ` */ charPosAbsolute(params: IParams): boolean;\n  /** CSI a */ hPositionRelative(params: IParams): boolean;\n  /** CSI b */ repeatPrecedingCharacter(params: IParams): boolean;\n  /** CSI c */ sendDeviceAttributesPrimary(params: IParams): boolean;\n  /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): boolean;\n  /** CSI d */ linePosAbsolute(params: IParams): boolean;\n  /** CSI e */ vPositionRelative(params: IParams): boolean;\n  /** CSI f */ hVPosition(params: IParams): boolean;\n  /** CSI g */ tabClear(params: IParams): boolean;\n  /** CSI h */ setMode(params: IParams, collect?: string): boolean;\n  /** CSI l */ resetMode(params: IParams, collect?: string): boolean;\n  /** CSI m */ charAttributes(params: IParams): boolean;\n  /** CSI n */ deviceStatus(params: IParams, collect?: string): boolean;\n  /** CSI p */ softReset(params: IParams, collect?: string): boolean;\n  /** CSI q */ setCursorStyle(params: IParams, collect?: string): boolean;\n  /** CSI r */ setScrollRegion(params: IParams, collect?: string): boolean;\n  /** CSI s */ saveCursor(params: IParams): boolean;\n  /** CSI u */ restoreCursor(params: IParams): boolean;\n  /** CSI ' } */ insertColumns(params: IParams): boolean;\n  /** CSI ' ~ */ deleteColumns(params: IParams): boolean;\n\n  /** OSC 0\n      OSC 2 */ setTitle(data: string): boolean;\n  /** OSC 4 */ setOrReportIndexedColor(data: string): boolean;\n  /** OSC 10 */ setOrReportFgColor(data: string): boolean;\n  /** OSC 11 */ setOrReportBgColor(data: string): boolean;\n  /** OSC 12 */ setOrReportCursorColor(data: string): boolean;\n  /** OSC 104 */ restoreIndexedColor(data: string): boolean;\n  /** OSC 110 */ restoreFgColor(data: string): boolean;\n  /** OSC 111 */ restoreBgColor(data: string): boolean;\n  /** OSC 112 */ restoreCursorColor(data: string): boolean;\n\n  /** ESC E */ nextLine(): boolean;\n  /** ESC = */ keypadApplicationMode(): boolean;\n  /** ESC > */ keypadNumericMode(): boolean;\n  /** ESC % G\n      ESC % @ */ selectDefaultCharset(): boolean;\n  /** ESC ( C\n      ESC ) C\n      ESC * C\n      ESC + C\n      ESC - C\n      ESC . C\n      ESC / C */ selectCharset(collectAndFlag: string): boolean;\n  /** ESC D */ index(): boolean;\n  /** ESC H */ tabSet(): boolean;\n  /** ESC M */ reverseIndex(): boolean;\n  /** ESC c */ fullReset(): boolean;\n  /** ESC n\n      ESC o\n      ESC |\n      ESC }\n      ESC ~ */ setgLevel(level: number): boolean;\n  /** ESC # 8 */ screenAlignmentPattern(): boolean;\n}\n\nexport interface IParseStack {\n  paused: boolean;\n  cursorStartX: number;\n  cursorStartY: number;\n  decodedLength: number;\n  position: number;\n}\n"
  },
  {
    "path": "src/common/Version.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * The xterm.js version. This is updated by the publish script from package.json.\n */\nexport const XTERM_VERSION = '6.0.0';\n"
  },
  {
    "path": "src/common/WindowsMode.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';\nimport { IBufferService } from 'common/services/Services';\n\nexport function updateWindowsModeWrappedState(bufferService: IBufferService): void {\n  // Winpty does not support wraparound mode which means that lines will never\n  // be marked as wrapped. This causes issues for things like copying a line\n  // retaining the wrapped new line characters or if consumers are listening\n  // in on the data stream.\n  //\n  // The workaround for this is to listen to every incoming line feed and mark\n  // the line as wrapped if the last character in the previous line is not a\n  // space. This is certainly not without its problems, but generally on\n  // Windows when text reaches the end of the terminal it's likely going to be\n  // wrapped.\n  const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1);\n  const lastChar = line?.get(bufferService.cols - 1);\n\n  const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y);\n  if (nextLine && lastChar) {\n    nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);\n  }\n}\n"
  },
  {
    "path": "src/common/buffer/AttributeData.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IAttributeData, IColorRGB, IExtendedAttrs } from 'common/Types';\nimport { Attributes, FgFlags, BgFlags, UnderlineStyle, ExtFlags } from 'common/buffer/Constants';\n\nexport class AttributeData implements IAttributeData {\n  public static toColorRGB(value: number): IColorRGB {\n    return [\n      value >>> Attributes.RED_SHIFT & 255,\n      value >>> Attributes.GREEN_SHIFT & 255,\n      value & 255\n    ];\n  }\n\n  public static fromColorRGB(value: IColorRGB): number {\n    return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;\n  }\n\n  public clone(): IAttributeData {\n    const newObj = new AttributeData();\n    newObj.fg = this.fg;\n    newObj.bg = this.bg;\n    newObj.extended = this.extended.clone();\n    return newObj;\n  }\n\n  // data\n  public fg = 0;\n  public bg = 0;\n  public extended: IExtendedAttrs = new ExtendedAttrs();\n\n  // flags\n  public isInverse(): number       { return this.fg & FgFlags.INVERSE; }\n  public isBold(): number          { return this.fg & FgFlags.BOLD; }\n  public isUnderline(): number     {\n    if (this.hasExtendedAttrs() && this.extended.underlineStyle !== UnderlineStyle.NONE) {\n      return 1;\n    }\n    return this.fg & FgFlags.UNDERLINE;\n  }\n  public isBlink(): number         { return this.fg & FgFlags.BLINK; }\n  public isInvisible(): number     { return this.fg & FgFlags.INVISIBLE; }\n  public isItalic(): number        { return this.bg & BgFlags.ITALIC; }\n  public isDim(): number           { return this.bg & BgFlags.DIM; }\n  public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; }\n  public isProtected(): number     { return this.bg & BgFlags.PROTECTED; }\n  public isOverline(): number      { return this.bg & BgFlags.OVERLINE; }\n\n  // color modes\n  public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }\n  public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }\n  public isFgRGB(): boolean       { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }\n  public isBgRGB(): boolean       { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }\n  public isFgPalette(): boolean   { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }\n  public isBgPalette(): boolean   { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }\n  public isFgDefault(): boolean   { return (this.fg & Attributes.CM_MASK) === 0; }\n  public isBgDefault(): boolean   { return (this.bg & Attributes.CM_MASK) === 0; }\n  public isAttributeDefault(): boolean { return this.fg === 0 && this.bg === 0; }\n\n  // colors\n  public getFgColor(): number {\n    switch (this.fg & Attributes.CM_MASK) {\n      case Attributes.CM_P16:\n      case Attributes.CM_P256:  return this.fg & Attributes.PCOLOR_MASK;\n      case Attributes.CM_RGB:   return this.fg & Attributes.RGB_MASK;\n      default:                  return -1;  // CM_DEFAULT defaults to -1\n    }\n  }\n  public getBgColor(): number {\n    switch (this.bg & Attributes.CM_MASK) {\n      case Attributes.CM_P16:\n      case Attributes.CM_P256:  return this.bg & Attributes.PCOLOR_MASK;\n      case Attributes.CM_RGB:   return this.bg & Attributes.RGB_MASK;\n      default:                  return -1;  // CM_DEFAULT defaults to -1\n    }\n  }\n\n  // extended attrs\n  public hasExtendedAttrs(): number {\n    return this.bg & BgFlags.HAS_EXTENDED;\n  }\n  public updateExtended(): void {\n    if (this.extended.isEmpty()) {\n      this.bg &= ~BgFlags.HAS_EXTENDED;\n    } else {\n      this.bg |= BgFlags.HAS_EXTENDED;\n    }\n  }\n  public getUnderlineColor(): number {\n    if ((this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor) {\n      switch (this.extended.underlineColor & Attributes.CM_MASK) {\n        case Attributes.CM_P16:\n        case Attributes.CM_P256:  return this.extended.underlineColor & Attributes.PCOLOR_MASK;\n        case Attributes.CM_RGB:   return this.extended.underlineColor & Attributes.RGB_MASK;\n        default:                  return this.getFgColor();\n      }\n    }\n    return this.getFgColor();\n  }\n  public getUnderlineColorMode(): number {\n    return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor\n      ? this.extended.underlineColor & Attributes.CM_MASK\n      : this.getFgColorMode();\n  }\n  public isUnderlineColorRGB(): boolean {\n    return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor\n      ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_RGB\n      : this.isFgRGB();\n  }\n  public isUnderlineColorPalette(): boolean {\n    return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor\n      ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P16\n          || (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P256\n      : this.isFgPalette();\n  }\n  public isUnderlineColorDefault(): boolean {\n    return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor\n      ? (this.extended.underlineColor & Attributes.CM_MASK) === 0\n      : this.isFgDefault();\n  }\n  public getUnderlineStyle(): UnderlineStyle {\n    return this.fg & FgFlags.UNDERLINE\n      ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)\n      : UnderlineStyle.NONE;\n  }\n  public getUnderlineVariantOffset(): number {\n    return this.extended.underlineVariantOffset;\n  }\n}\n\n\n/**\n * Extended attributes for a cell.\n * Holds information about different underline styles and color.\n */\nexport class ExtendedAttrs implements IExtendedAttrs {\n  private _ext: number = 0;\n  public get ext(): number {\n    if (this._urlId) {\n      return (\n        (this._ext & ~ExtFlags.UNDERLINE_STYLE) |\n        (this.underlineStyle << 26)\n      );\n    }\n    return this._ext;\n  }\n  public set ext(value: number) { this._ext = value; }\n\n  public get underlineStyle(): UnderlineStyle {\n    // Always return the URL style if it has one\n    if (this._urlId) {\n      return UnderlineStyle.DASHED;\n    }\n    return (this._ext & ExtFlags.UNDERLINE_STYLE) >> 26;\n  }\n  public set underlineStyle(value: UnderlineStyle) {\n    this._ext &= ~ExtFlags.UNDERLINE_STYLE;\n    this._ext |= (value << 26) & ExtFlags.UNDERLINE_STYLE;\n  }\n\n  public get underlineColor(): number {\n    return this._ext & (Attributes.CM_MASK | Attributes.RGB_MASK);\n  }\n  public set underlineColor(value: number) {\n    this._ext &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n    this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK);\n  }\n\n  private _urlId: number = 0;\n  public get urlId(): number {\n    return this._urlId;\n  }\n  public set urlId(value: number) {\n    this._urlId = value;\n  }\n\n  public get underlineVariantOffset(): number {\n    const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;\n    if (val < 0) {\n      return val ^ 0xFFFFFFF8;\n    }\n    return val;\n  }\n  public set underlineVariantOffset(value: number) {\n    this._ext &= ~ExtFlags.VARIANT_OFFSET;\n    this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;\n  }\n\n  constructor(\n    ext: number = 0,\n    urlId: number = 0\n  ) {\n    this._ext = ext;\n    this._urlId = urlId;\n  }\n\n  public clone(): IExtendedAttrs {\n    return new ExtendedAttrs(this._ext, this._urlId);\n  }\n\n  /**\n   * Convenient method to indicate whether the object holds no additional information,\n   * that needs to be persistant in the buffer.\n   */\n  public isEmpty(): boolean {\n    return this.underlineStyle === UnderlineStyle.NONE && this._urlId === 0;\n  }\n}\n"
  },
  {
    "path": "src/common/buffer/Buffer.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { Buffer } from 'common/buffer/Buffer';\nimport { CircularList } from 'common/CircularList';\nimport { MockOptionsService, MockBufferService, MockLogService, createCellData } from 'common/TestUtils.test';\nimport { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { CellData } from 'common/buffer/CellData';\nimport { ExtendedAttrs } from 'common/buffer/AttributeData';\n\nconst INIT_COLS = 80;\nconst INIT_ROWS = 24;\nconst INIT_SCROLLBACK = 1000;\n\ndescribe('Buffer', () => {\n  let optionsService: MockOptionsService;\n  let bufferService: MockBufferService;\n  let buffer: Buffer;\n\n  beforeEach(() => {\n    optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK });\n    bufferService = new MockBufferService(INIT_COLS, INIT_ROWS);\n    buffer = new Buffer(true, optionsService, bufferService, new MockLogService());\n  });\n\n  describe('constructor', () => {\n    it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => {\n      assert.instanceOf(buffer.lines, CircularList);\n      assert.equal(buffer.lines.maxLength, bufferService.rows + INIT_SCROLLBACK);\n    });\n    it('should set the Buffer\\'s scrollBottom value equal to the terminal\\'s rows -1', () => {\n      assert.equal(buffer.scrollBottom, bufferService.rows - 1);\n    });\n  });\n\n  describe('fillViewportRows', () => {\n    it('should fill the buffer with blank lines based on the size of the viewport', () => {\n      const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR_DATA).loadCell(0, new CellData()).getAsCharData();\n      buffer.fillViewportRows();\n      assert.equal(buffer.lines.length, INIT_ROWS);\n      for (let y = 0; y < INIT_ROWS; y++) {\n        assert.equal(buffer.lines.get(y)!.length, INIT_COLS);\n        for (let x = 0; x < INIT_COLS; x++) {\n          assert.deepEqual(buffer.lines.get(y)!.loadCell(x, new CellData()).getAsCharData(), blankLineChar);\n        }\n      }\n    });\n  });\n\n  describe('getWrappedRangeForLine', () => {\n    describe('non-wrapped', () => {\n      it('should return a single row for the first row', () => {\n        buffer.fillViewportRows();\n        assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 0 });\n      });\n      it('should return a single row for a middle row', () => {\n        buffer.fillViewportRows();\n        assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 12 });\n      });\n      it('should return a single row for the last row', () => {\n        buffer.fillViewportRows();\n        assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 23, last: 23 });\n      });\n    });\n    describe('wrapped', () => {\n      it('should return a range for the first row', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(1)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 1 });\n      });\n      it('should return a range for a middle row wrapping upwards', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(12)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 11, last: 12 });\n      });\n      it('should return a range for a middle row wrapping downwards', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(13)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 13 });\n      });\n      it('should return a range for a middle row wrapping both ways', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(11)!.isWrapped = true;\n        buffer.lines.get(12)!.isWrapped = true;\n        buffer.lines.get(13)!.isWrapped = true;\n        buffer.lines.get(14)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 10, last: 14 });\n      });\n      it('should return a range for the last row', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(23)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 22, last: 23 });\n      });\n      it('should return a range for a row that wraps upward to first row', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(1)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(1), { first: 0, last: 1 });\n      });\n      it('should return a range for a row that wraps downward to last row', () => {\n        buffer.fillViewportRows();\n        buffer.lines.get(buffer.lines.length - 1)!.isWrapped = true;\n        assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 2), { first: 22, last: 23 });\n      });\n    });\n  });\n\n  describe('resize', () => {\n    describe('column size is reduced', () => {\n      it('should trim the data in the buffer', () => {\n        buffer.fillViewportRows();\n        buffer.resize(INIT_COLS / 2, INIT_ROWS);\n        assert.equal(buffer.lines.length, INIT_ROWS);\n        for (let i = 0; i < INIT_ROWS; i++) {\n          assert.equal(buffer.lines.get(i)!.length, INIT_COLS / 2);\n        }\n      });\n    });\n\n    describe('column size is increased', () => {\n      it('should add pad columns', () => {\n        buffer.fillViewportRows();\n        buffer.resize(INIT_COLS + 10, INIT_ROWS);\n        assert.equal(buffer.lines.length, INIT_ROWS);\n        for (let i = 0; i < INIT_ROWS; i++) {\n          assert.equal(buffer.lines.get(i)!.length, INIT_COLS + 10);\n        }\n      });\n    });\n\n    describe('row size reduced', () => {\n      it('should trim blank lines from the end', () => {\n        buffer.fillViewportRows();\n        buffer.resize(INIT_COLS, INIT_ROWS - 10);\n        assert.equal(buffer.lines.length, INIT_ROWS - 10);\n      });\n\n      it('should move the viewport down when it\\'s at the end', () => {\n        buffer.fillViewportRows();\n        // Set cursor y to have 5 blank lines below it\n        buffer.y = INIT_ROWS - 5 - 1;\n        buffer.resize(INIT_COLS, INIT_ROWS - 10);\n        // Trim 5 rows\n        assert.equal(buffer.lines.length, INIT_ROWS - 5);\n        // Shift the viewport down 5 rows\n        assert.equal(buffer.ydisp, 5);\n        assert.equal(buffer.ybase, 5);\n      });\n\n      describe('no scrollback', () => {\n        it('should trim from the top of the buffer when the cursor reaches the bottom', () => {\n          buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());\n          assert.equal(buffer.lines.maxLength, INIT_ROWS);\n          buffer.y = INIT_ROWS - 1;\n          buffer.fillViewportRows();\n          let chData = buffer.lines.get(5)!.loadCell(0, new CellData()).getAsCharData();\n          chData[1] = 'a';\n          buffer.lines.get(5)!.setCell(0, CellData.fromCharData(chData));\n          chData = buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getAsCharData();\n          chData[1] = 'b';\n          buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData(chData));\n          buffer.resize(INIT_COLS, INIT_ROWS - 5);\n          assert.equal(buffer.lines.get(0)!.loadCell(0, new CellData()).getAsCharData()[1], 'a');\n          assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5)!.loadCell(0, new CellData()).getAsCharData()[1], 'b');\n        });\n      });\n    });\n\n    describe('row size increased', () => {\n      describe('empty buffer', () => {\n        it('should add blank lines to end', () => {\n          buffer.fillViewportRows();\n          assert.equal(buffer.ydisp, 0);\n          buffer.resize(INIT_COLS, INIT_ROWS + 10);\n          assert.equal(buffer.ydisp, 0);\n          assert.equal(buffer.lines.length, INIT_ROWS + 10);\n        });\n      });\n\n      describe('filled buffer', () => {\n        it('should show more of the buffer above', () => {\n          buffer.fillViewportRows();\n          // Create 10 extra blank lines\n          for (let i = 0; i < 10; i++) {\n            buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA));\n          }\n          // Set cursor to the bottom of the buffer\n          buffer.y = INIT_ROWS - 1;\n          // Scroll down 10 lines\n          buffer.ybase = 10;\n          buffer.ydisp = 10;\n          assert.equal(buffer.lines.length, INIT_ROWS + 10);\n          buffer.resize(INIT_COLS, INIT_ROWS + 5);\n          // Should be should 5 more lines\n          assert.equal(buffer.ydisp, 5);\n          assert.equal(buffer.ybase, 5);\n          // Should not trim the buffer\n          assert.equal(buffer.lines.length, INIT_ROWS + 10);\n        });\n\n        it('should show more of the buffer below when the viewport is at the top of the buffer', () => {\n          buffer.fillViewportRows();\n          // Create 10 extra blank lines\n          for (let i = 0; i < 10; i++) {\n            buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA));\n          }\n          // Set cursor to the bottom of the buffer\n          buffer.y = INIT_ROWS - 1;\n          // Scroll down 10 lines\n          buffer.ybase = 10;\n          buffer.ydisp = 0;\n          assert.equal(buffer.lines.length, INIT_ROWS + 10);\n          buffer.resize(INIT_COLS, INIT_ROWS + 5);\n          // The viewport should remain at the top\n          assert.equal(buffer.ydisp, 0);\n          // The buffer ybase should move up 5 lines\n          assert.equal(buffer.ybase, 5);\n          // Should not trim the buffer\n          assert.equal(buffer.lines.length, INIT_ROWS + 10);\n        });\n      });\n    });\n\n    describe('row and column increased', () => {\n      it('should resize properly', () => {\n        buffer.fillViewportRows();\n        buffer.resize(INIT_COLS + 5, INIT_ROWS + 5);\n        assert.equal(buffer.lines.length, INIT_ROWS + 5);\n        for (let i = 0; i < INIT_ROWS + 5; i++) {\n          assert.equal(buffer.lines.get(i)!.length, INIT_COLS + 5);\n        }\n      });\n    });\n\n    describe('reflow', () => {\n      it('should not wrap empty lines', () => {\n        buffer.fillViewportRows();\n        assert.equal(buffer.lines.length, INIT_ROWS);\n        buffer.resize(INIT_COLS - 5, INIT_ROWS);\n        assert.equal(buffer.lines.length, INIT_ROWS);\n      });\n      it('should shrink row length', () => {\n        buffer.fillViewportRows();\n        buffer.resize(5, 10);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.length, 5);\n        assert.equal(buffer.lines.get(1)!.length, 5);\n        assert.equal(buffer.lines.get(2)!.length, 5);\n        assert.equal(buffer.lines.get(3)!.length, 5);\n        assert.equal(buffer.lines.get(4)!.length, 5);\n        assert.equal(buffer.lines.get(5)!.length, 5);\n        assert.equal(buffer.lines.get(6)!.length, 5);\n        assert.equal(buffer.lines.get(7)!.length, 5);\n        assert.equal(buffer.lines.get(8)!.length, 5);\n        assert.equal(buffer.lines.get(9)!.length, 5);\n      });\n      it('should wrap and unwrap lines', () => {\n        buffer.fillViewportRows();\n        buffer.resize(5, 10);\n        const firstLine = buffer.lines.get(0)!;\n        for (let i = 0; i < 5; i++) {\n          const code = 'a'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          firstLine.set(i, [0, char, 1, code]);\n        }\n        buffer.y = 1;\n        assert.equal(buffer.lines.get(0)!.length, 5);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcde');\n        buffer.resize(1, 10);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'a');\n        assert.equal(buffer.lines.get(1)!.translateToString(), 'b');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'c');\n        assert.equal(buffer.lines.get(3)!.translateToString(), 'd');\n        assert.equal(buffer.lines.get(4)!.translateToString(), 'e');\n        assert.equal(buffer.lines.get(5)!.translateToString(), ' ');\n        assert.equal(buffer.lines.get(6)!.translateToString(), ' ');\n        assert.equal(buffer.lines.get(7)!.translateToString(), ' ');\n        assert.equal(buffer.lines.get(8)!.translateToString(), ' ');\n        assert.equal(buffer.lines.get(9)!.translateToString(), ' ');\n        buffer.resize(5, 10);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcde');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(2)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(3)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(4)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(5)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(6)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(7)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(8)!.translateToString(), '     ');\n        assert.equal(buffer.lines.get(9)!.translateToString(), '     ');\n      });\n      it('should discard parts of wrapped lines that go out of the scrollback', () => {\n        buffer.fillViewportRows();\n        optionsService.options.scrollback = 1;\n        buffer.resize(10, 5);\n        const lastLine = buffer.lines.get(3)!;\n        for (let i = 0; i < 10; i++) {\n          const code = 'a'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          lastLine.set(i, [0, char, 1, code]);\n        }\n        assert.equal(buffer.lines.length, 5);\n        buffer.y = 4;\n        buffer.resize(2, 5);\n        assert.equal(buffer.y, 4);\n        assert.equal(buffer.ybase, 1);\n        assert.equal(buffer.lines.length, 6);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'ab');\n        assert.equal(buffer.lines.get(1)!.translateToString(), 'cd');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'ef');\n        assert.equal(buffer.lines.get(3)!.translateToString(), 'gh');\n        assert.equal(buffer.lines.get(4)!.translateToString(), 'ij');\n        assert.equal(buffer.lines.get(5)!.translateToString(), '  ');\n        buffer.resize(1, 5);\n        assert.equal(buffer.y, 4);\n        assert.equal(buffer.ybase, 1);\n        assert.equal(buffer.lines.length, 6);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'f');\n        assert.equal(buffer.lines.get(1)!.translateToString(), 'g');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'h');\n        assert.equal(buffer.lines.get(3)!.translateToString(), 'i');\n        assert.equal(buffer.lines.get(4)!.translateToString(), 'j');\n        assert.equal(buffer.lines.get(5)!.translateToString(), ' ');\n        buffer.resize(10, 5);\n        assert.equal(buffer.y, 1);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 5);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'fghij     ');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '          ');\n        assert.equal(buffer.lines.get(2)!.translateToString(), '          ');\n        assert.equal(buffer.lines.get(3)!.translateToString(), '          ');\n        assert.equal(buffer.lines.get(4)!.translateToString(), '          ');\n      });\n      it('should remove the correct amount of rows when reflowing larger', () => {\n        // This is a regression test to ensure that successive wrapped lines that are getting\n        // 3+ lines removed on a reflow actually remove the right lines\n        buffer.fillViewportRows();\n        buffer.resize(10, 10);\n        buffer.y = 2;\n        const firstLine = buffer.lines.get(0)!;\n        const secondLine = buffer.lines.get(1)!;\n        for (let i = 0; i < 10; i++) {\n          const code = 'a'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          firstLine.set(i, [0, char, 1, code]);\n        }\n        for (let i = 0; i < 10; i++) {\n          const code = '0'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          secondLine.set(i, [0, char, 1, code]);\n        }\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789');\n        for (let i = 2; i < 10; i++) {\n          assert.equal(buffer.lines.get(i)!.translateToString(), '          ');\n        }\n        buffer.resize(2, 10);\n        assert.equal(buffer.ybase, 1);\n        assert.equal(buffer.lines.length, 11);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'ab');\n        assert.equal(buffer.lines.get(1)!.translateToString(), 'cd');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'ef');\n        assert.equal(buffer.lines.get(3)!.translateToString(), 'gh');\n        assert.equal(buffer.lines.get(4)!.translateToString(), 'ij');\n        assert.equal(buffer.lines.get(5)!.translateToString(), '01');\n        assert.equal(buffer.lines.get(6)!.translateToString(), '23');\n        assert.equal(buffer.lines.get(7)!.translateToString(), '45');\n        assert.equal(buffer.lines.get(8)!.translateToString(), '67');\n        assert.equal(buffer.lines.get(9)!.translateToString(), '89');\n        assert.equal(buffer.lines.get(10)!.translateToString(), '  ');\n        buffer.resize(10, 10);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789');\n        for (let i = 2; i < 10; i++) {\n          assert.equal(buffer.lines.get(i)!.translateToString(), '          ');\n        }\n      });\n      it('should transfer combined char data over to reflowed lines', () => {\n        buffer.fillViewportRows();\n        buffer.resize(4, 3);\n        buffer.y = 2;\n        const firstLine = buffer.lines.get(0)!;\n        firstLine.set(0, [ 0, 'a', 1, 'a'.charCodeAt(0) ]);\n        firstLine.set(1, [ 0, 'b', 1, 'b'.charCodeAt(0) ]);\n        firstLine.set(2, [ 0, 'c', 1, 'c'.charCodeAt(0) ]);\n        firstLine.set(3, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);\n        assert.equal(buffer.lines.length, 3);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abc😁');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '    ');\n        buffer.resize(2, 3);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'ab');\n        assert.equal(buffer.lines.get(1)!.translateToString(), 'c😁');\n      });\n      it('should adjust markers when reflowing', () => {\n        buffer.fillViewportRows();\n        buffer.resize(10, 16);\n        for (let i = 0; i < 10; i++) {\n          const code = 'a'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          buffer.lines.get(0)!.set(i, [0, char, 1, code]);\n        }\n        for (let i = 0; i < 10; i++) {\n          const code = '0'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          buffer.lines.get(1)!.set(i, [0, char, 1, code]);\n        }\n        for (let i = 0; i < 10; i++) {\n          const code = 'k'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          buffer.lines.get(2)!.set(i, [0, char, 1, code]);\n        }\n        buffer.y = 3;\n        // Buffer:\n        // abcdefghij\n        // 0123456789\n        // abcdefghij\n        const firstMarker = buffer.addMarker(0);\n        const secondMarker = buffer.addMarker(1);\n        const thirdMarker = buffer.addMarker(2);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst');\n        assert.equal(firstMarker.line, 0);\n        assert.equal(secondMarker.line, 1);\n        assert.equal(thirdMarker.line, 2);\n        buffer.resize(2, 16);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'ab');\n        assert.equal(buffer.lines.get(1)!.translateToString(), 'cd');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'ef');\n        assert.equal(buffer.lines.get(3)!.translateToString(), 'gh');\n        assert.equal(buffer.lines.get(4)!.translateToString(), 'ij');\n        assert.equal(buffer.lines.get(5)!.translateToString(), '01');\n        assert.equal(buffer.lines.get(6)!.translateToString(), '23');\n        assert.equal(buffer.lines.get(7)!.translateToString(), '45');\n        assert.equal(buffer.lines.get(8)!.translateToString(), '67');\n        assert.equal(buffer.lines.get(9)!.translateToString(), '89');\n        assert.equal(buffer.lines.get(10)!.translateToString(), 'kl');\n        assert.equal(buffer.lines.get(11)!.translateToString(), 'mn');\n        assert.equal(buffer.lines.get(12)!.translateToString(), 'op');\n        assert.equal(buffer.lines.get(13)!.translateToString(), 'qr');\n        assert.equal(buffer.lines.get(14)!.translateToString(), 'st');\n        assert.equal(firstMarker.line, 0, 'first marker should remain unchanged');\n        assert.equal(secondMarker.line, 5, 'second marker should be shifted since the first line wrapped');\n        assert.equal(thirdMarker.line, 10, 'third marker should be shifted since the first and second lines wrapped');\n        buffer.resize(10, 16);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst');\n        assert.equal(firstMarker.line, 0, 'first marker should remain unchanged');\n        assert.equal(secondMarker.line, 1, 'second marker should be restored to it\\'s original line');\n        assert.equal(thirdMarker.line, 2, 'third marker should be restored to it\\'s original line');\n        assert.equal(firstMarker.isDisposed, false);\n        assert.equal(secondMarker.isDisposed, false);\n        assert.equal(thirdMarker.isDisposed, false);\n      });\n      it('should dispose markers whose rows are trimmed during a reflow', () => {\n        buffer.fillViewportRows();\n        optionsService.options.scrollback = 1;\n        buffer.resize(10, 11);\n        for (let i = 0; i < 10; i++) {\n          const code = 'a'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          buffer.lines.get(0)!.set(i, [0, char, 1, code]);\n        }\n        for (let i = 0; i < 10; i++) {\n          const code = '0'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          buffer.lines.get(1)!.set(i, [0, char, 1, code]);\n        }\n        for (let i = 0; i < 10; i++) {\n          const code = 'k'.charCodeAt(0) + i;\n          const char = String.fromCharCode(code);\n          buffer.lines.get(2)!.set(i, [0, char, 1, code]);\n        }\n        buffer.y = 10;\n        // Buffer:\n        // abcdefghij\n        // 0123456789\n        // abcdefghij\n        const firstMarker = buffer.addMarker(0);\n        const secondMarker = buffer.addMarker(1);\n        const thirdMarker = buffer.addMarker(2);\n        buffer.y = 3;\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst');\n        assert.equal(firstMarker.line, 0);\n        assert.equal(secondMarker.line, 1);\n        assert.equal(thirdMarker.line, 2);\n        buffer.resize(2, 11);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'ij');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '01');\n        assert.equal(buffer.lines.get(2)!.translateToString(), '23');\n        assert.equal(buffer.lines.get(3)!.translateToString(), '45');\n        assert.equal(buffer.lines.get(4)!.translateToString(), '67');\n        assert.equal(buffer.lines.get(5)!.translateToString(), '89');\n        assert.equal(buffer.lines.get(6)!.translateToString(), 'kl');\n        assert.equal(buffer.lines.get(7)!.translateToString(), 'mn');\n        assert.equal(buffer.lines.get(8)!.translateToString(), 'op');\n        assert.equal(buffer.lines.get(9)!.translateToString(), 'qr');\n        assert.equal(buffer.lines.get(10)!.translateToString(), 'st');\n        assert.equal(secondMarker.line, 1, 'second marker should remain the same as it was shifted 4 and trimmed 4');\n        assert.equal(thirdMarker.line, 6, 'third marker should be shifted since the first and second lines wrapped');\n        assert.equal(firstMarker.isDisposed, true, 'first marker was trimmed');\n        assert.equal(secondMarker.isDisposed, false);\n        assert.equal(thirdMarker.isDisposed, false);\n        buffer.resize(10, 11);\n        assert.equal(buffer.lines.get(0)!.translateToString(), 'ij        ');\n        assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789');\n        assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst');\n        assert.equal(secondMarker.line, 1, 'second marker should be restored');\n        assert.equal(thirdMarker.line, 2, 'third marker should be restored');\n      });\n      it('should correctly reflow wrapped lines that end in 0 space (via tab char)', () => {\n        buffer.fillViewportRows();\n        buffer.resize(4, 10);\n        buffer.y = 2;\n        buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n        buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]);\n        buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]);\n        buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]);\n        buffer.lines.get(1)!.isWrapped = true;\n        // Buffer:\n        // \"ab  \" (wrapped)\n        // \"cd\"\n        buffer.resize(5, 10);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), 'ab  c');\n        assert.equal(buffer.lines.get(1)!.translateToString(false), 'd    ');\n        buffer.resize(6, 10);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), 'ab  cd');\n        assert.equal(buffer.lines.get(1)!.translateToString(false), '      ');\n      });\n      it('should wrap wide characters correctly when reflowing larger', () => {\n        buffer.fillViewportRows();\n        buffer.resize(12, 10);\n        buffer.y = 2;\n        for (let i = 0; i < 12; i += 4) {\n          buffer.lines.get(0)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]);\n        }\n        for (let i = 2; i < 12; i += 4) {\n          buffer.lines.get(0)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]);\n        }\n        for (let i = 1; i < 12; i += 2) {\n          buffer.lines.get(0)!.set(i, [0, '', 0, 0]);\n          buffer.lines.get(1)!.set(i, [0, '', 0, 0]);\n        }\n        buffer.lines.get(1)!.isWrapped = true;\n        // Buffer:\n        // 汉语汉语汉语 (wrapped)\n        // 汉语汉语汉语\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语汉语');\n        buffer.resize(13, 10);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语');\n        assert.equal(buffer.lines.get(0)!.translateToString(false), '汉语汉语汉语 ');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语汉语');\n        assert.equal(buffer.lines.get(1)!.translateToString(false), '汉语汉语汉语 ');\n        buffer.resize(14, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语汉');\n        assert.equal(buffer.lines.get(0)!.translateToString(false), '汉语汉语汉语汉');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语汉语');\n        assert.equal(buffer.lines.get(1)!.translateToString(false), '语汉语汉语    ');\n      });\n      it('should correctly reflow wrapped lines that end in 0 space (via tab char)', () => {\n        buffer.fillViewportRows();\n        buffer.resize(4, 10);\n        buffer.y = 2;\n        buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n        buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]);\n        buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]);\n        buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]);\n        buffer.lines.get(1)!.isWrapped = true;\n        // Buffer:\n        // \"ab  \" (wrapped)\n        // \"cd\"\n        buffer.resize(3, 10);\n        assert.equal(buffer.y, 2);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(false), 'ab ');\n        assert.equal(buffer.lines.get(1)!.translateToString(false), ' cd');\n        buffer.resize(2, 10);\n        assert.equal(buffer.y, 3);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(false), 'ab');\n        assert.equal(buffer.lines.get(1)!.translateToString(false), '  ');\n        assert.equal(buffer.lines.get(2)!.translateToString(false), 'cd');\n      });\n      it('should wrap wide characters correctly when reflowing smaller', () => {\n        buffer.fillViewportRows();\n        buffer.resize(12, 10);\n        buffer.y = 2;\n        for (let i = 0; i < 12; i += 4) {\n          buffer.lines.get(0)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]);\n        }\n        for (let i = 2; i < 12; i += 4) {\n          buffer.lines.get(0)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]);\n        }\n        for (let i = 1; i < 12; i += 2) {\n          buffer.lines.get(0)!.set(i, [0, '', 0, 0]);\n          buffer.lines.get(1)!.set(i, [0, '', 0, 0]);\n        }\n        buffer.lines.get(1)!.isWrapped = true;\n        // Buffer:\n        // 汉语汉语汉语 (wrapped)\n        // 汉语汉语汉语\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语汉语');\n        buffer.resize(11, 10);\n        assert.equal(buffer.ybase, 0);\n        assert.equal(buffer.lines.length, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语汉语');\n        assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语');\n        buffer.resize(10, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语汉语');\n        assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语');\n        buffer.resize(9, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语');\n        assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉语');\n        buffer.resize(8, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语');\n        assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉语');\n        buffer.resize(7, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语');\n        assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉');\n        assert.equal(buffer.lines.get(3)!.translateToString(true), '语汉语');\n        buffer.resize(6, 10);\n        assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉');\n        assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语');\n        assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉');\n        assert.equal(buffer.lines.get(3)!.translateToString(true), '语汉语');\n      });\n\n      describe('reflowLarger cases', () => {\n        beforeEach(() => {\n          // Setup buffer state:\n          // 'ab'\n          // 'cd' (wrapped)\n          // 'ef'\n          // 'gh' (wrapped)\n          // 'ij'\n          // 'kl' (wrapped)\n          // '  '\n          // '  '\n          // '  '\n          // '  '\n          buffer.fillViewportRows();\n          buffer.resize(2, 10);\n          buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n          buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]);\n          buffer.lines.get(1)!.isWrapped = true;\n          buffer.lines.get(2)!.set(0, [0, 'e', 1, 'e'.charCodeAt(0)]);\n          buffer.lines.get(2)!.set(1, [0, 'f', 1, 'f'.charCodeAt(0)]);\n          buffer.lines.get(3)!.set(0, [0, 'g', 1, 'g'.charCodeAt(0)]);\n          buffer.lines.get(3)!.set(1, [0, 'h', 1, 'h'.charCodeAt(0)]);\n          buffer.lines.get(3)!.isWrapped = true;\n          buffer.lines.get(4)!.set(0, [0, 'i', 1, 'i'.charCodeAt(0)]);\n          buffer.lines.get(4)!.set(1, [0, 'j', 1, 'j'.charCodeAt(0)]);\n          buffer.lines.get(5)!.set(0, [0, 'k', 1, 'k'.charCodeAt(0)]);\n          buffer.lines.get(5)!.set(1, [0, 'l', 1, 'l'.charCodeAt(0)]);\n          buffer.lines.get(5)!.isWrapped = true;\n        });\n        describe('viewport not yet filled', () => {\n          it('should move the cursor up and add empty lines', () => {\n            buffer.y = 6;\n            buffer.resize(4, 10);\n            assert.equal(buffer.y, 3);\n            assert.equal(buffer.ydisp, 0);\n            assert.equal(buffer.ybase, 0);\n            assert.equal(buffer.lines.length, 10);\n            assert.equal(buffer.lines.get(0)!.translateToString(), 'abcd');\n            assert.equal(buffer.lines.get(1)!.translateToString(), 'efgh');\n            assert.equal(buffer.lines.get(2)!.translateToString(), 'ijkl');\n            for (let i = 3; i < 10; i++) {\n              assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n            }\n            const wrappedLines: number[] = [];\n            for (let i = 0; i < buffer.lines.length; i++) {\n              assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n            }\n          });\n        });\n        describe('viewport filled, scrollback remaining', () => {\n          beforeEach(() => {\n            buffer.y = 9;\n          });\n          describe('ybase === 0', () => {\n            it('should move the cursor up and add empty lines', () => {\n              buffer.resize(4, 10);\n              assert.equal(buffer.y, 6);\n              assert.equal(buffer.ydisp, 0);\n              assert.equal(buffer.ybase, 0);\n              assert.equal(buffer.lines.length, 10);\n              assert.equal(buffer.lines.get(0)!.translateToString(), 'abcd');\n              assert.equal(buffer.lines.get(1)!.translateToString(), 'efgh');\n              assert.equal(buffer.lines.get(2)!.translateToString(), 'ijkl');\n              for (let i = 3; i < 10; i++) {\n                assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n              }\n              const wrappedLines: number[] = [];\n              for (let i = 0; i < buffer.lines.length; i++) {\n                assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n              }\n            });\n          });\n          describe('ybase !== 0', () => {\n            beforeEach(() => {\n              // Add 10 empty rows to start\n              for (let i = 0; i < 10; i++) {\n                buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));\n              }\n              buffer.ybase = 10;\n            });\n            describe('&& ydisp === ybase', () => {\n              it('should adjust the viewport and keep ydisp = ybase', () => {\n                buffer.ydisp = 10;\n                buffer.resize(4, 10);\n                assert.equal(buffer.y, 9);\n                assert.equal(buffer.ydisp, 7);\n                assert.equal(buffer.ybase, 7);\n                assert.equal(buffer.lines.length, 17);\n                for (let i = 0; i < 10; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl');\n                for (let i = 13; i < 17; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                const wrappedLines: number[] = [];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n            describe('&& ydisp !== ybase', () => {\n              it('should keep ydisp at the same value', () => {\n                buffer.ydisp = 5;\n                buffer.resize(4, 10);\n                assert.equal(buffer.y, 9);\n                assert.equal(buffer.ydisp, 5);\n                assert.equal(buffer.ybase, 7);\n                assert.equal(buffer.lines.length, 17);\n                for (let i = 0; i < 10; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl');\n                for (let i = 13; i < 17; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                const wrappedLines: number[] = [];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n          });\n        });\n        describe('viewport filled, no scrollback remaining', () => {\n          // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported\n          describe('ybase !== 0', () => {\n            beforeEach(() => {\n              optionsService.options.scrollback = 10;\n              // Add 10 empty rows to start\n              for (let i = 0; i < 10; i++) {\n                buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));\n              }\n              buffer.y = 9;\n              buffer.ybase = 10;\n            });\n            describe('&& ydisp === ybase', () => {\n              it('should trim lines and keep ydisp = ybase', () => {\n                buffer.ydisp = 10;\n                buffer.resize(4, 10);\n                assert.equal(buffer.y, 9);\n                assert.equal(buffer.ydisp, 7);\n                assert.equal(buffer.ybase, 7);\n                assert.equal(buffer.lines.length, 17);\n                for (let i = 0; i < 10; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl');\n                for (let i = 13; i < 17; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                const wrappedLines: number[] = [];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n            describe('&& ydisp !== ybase', () => {\n              it('should trim lines and not change ydisp', () => {\n                buffer.ydisp = 5;\n                buffer.resize(4, 10);\n                assert.equal(buffer.y, 9);\n                assert.equal(buffer.ydisp, 5);\n                assert.equal(buffer.ybase, 7);\n                assert.equal(buffer.lines.length, 17);\n                for (let i = 0; i < 10; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl');\n                for (let i = 13; i < 17; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '    ');\n                }\n                const wrappedLines: number[] = [];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n          });\n        });\n      });\n      describe('reflowSmaller cases', () => {\n        beforeEach(() => {\n          // Setup buffer state:\n          // 'abcd'\n          // 'efgh' (wrapped)\n          // 'ijkl'\n          // '    '\n          // '    '\n          // '    '\n          // '    '\n          // '    '\n          // '    '\n          // '    '\n          buffer.fillViewportRows();\n          buffer.resize(4, 10);\n          buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n          buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]);\n          buffer.lines.get(0)!.set(2, [0, 'c', 1, 'c'.charCodeAt(0)]);\n          buffer.lines.get(0)!.set(3, [0, 'd', 1, 'd'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(0, [0, 'e', 1, 'e'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(1, [0, 'f', 1, 'f'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(2, [0, 'g', 1, 'g'.charCodeAt(0)]);\n          buffer.lines.get(1)!.set(3, [0, 'h', 1, 'h'.charCodeAt(0)]);\n          buffer.lines.get(2)!.set(0, [0, 'i', 1, 'i'.charCodeAt(0)]);\n          buffer.lines.get(2)!.set(1, [0, 'j', 1, 'j'.charCodeAt(0)]);\n          buffer.lines.get(2)!.set(2, [0, 'k', 1, 'k'.charCodeAt(0)]);\n          buffer.lines.get(2)!.set(3, [0, 'l', 1, 'l'.charCodeAt(0)]);\n        });\n        describe('viewport not yet filled', () => {\n          it('should move the cursor down', () => {\n            buffer.y = 3;\n            buffer.resize(2, 10);\n            assert.equal(buffer.y, 6);\n            assert.equal(buffer.ydisp, 0);\n            assert.equal(buffer.ybase, 0);\n            assert.equal(buffer.lines.length, 10);\n            assert.equal(buffer.lines.get(0)!.translateToString(), 'ab');\n            assert.equal(buffer.lines.get(1)!.translateToString(), 'cd');\n            assert.equal(buffer.lines.get(2)!.translateToString(), 'ef');\n            assert.equal(buffer.lines.get(3)!.translateToString(), 'gh');\n            assert.equal(buffer.lines.get(4)!.translateToString(), 'ij');\n            assert.equal(buffer.lines.get(5)!.translateToString(), 'kl');\n            for (let i = 6; i < 10; i++) {\n              assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n            }\n            const wrappedLines = [1, 3, 5];\n            for (let i = 0; i < buffer.lines.length; i++) {\n              assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n            }\n          });\n        });\n        describe('viewport filled, scrollback remaining', () => {\n          beforeEach(() => {\n            buffer.y = 9;\n          });\n          describe('ybase === 0', () => {\n            it('should trim the top', () => {\n              buffer.resize(2, 10);\n              assert.equal(buffer.y, 9);\n              assert.equal(buffer.ydisp, 3);\n              assert.equal(buffer.ybase, 3);\n              assert.equal(buffer.lines.length, 13);\n              assert.equal(buffer.lines.get(0)!.translateToString(), 'ab');\n              assert.equal(buffer.lines.get(1)!.translateToString(), 'cd');\n              assert.equal(buffer.lines.get(2)!.translateToString(), 'ef');\n              assert.equal(buffer.lines.get(3)!.translateToString(), 'gh');\n              assert.equal(buffer.lines.get(4)!.translateToString(), 'ij');\n              assert.equal(buffer.lines.get(5)!.translateToString(), 'kl');\n              for (let i = 6; i < 13; i++) {\n                assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n              }\n              const wrappedLines = [1, 3, 5];\n              for (let i = 0; i < buffer.lines.length; i++) {\n                assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n              }\n            });\n          });\n          describe('ybase !== 0', () => {\n            beforeEach(() => {\n              // Add 10 empty rows to start\n              for (let i = 0; i < 10; i++) {\n                buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));\n              }\n              buffer.ybase = 10;\n            });\n            describe('&& ydisp === ybase', () => {\n              it('should adjust the viewport and keep ydisp = ybase', () => {\n                buffer.ydisp = 10;\n                buffer.resize(2, 10);\n                assert.equal(buffer.ydisp, 13);\n                assert.equal(buffer.ybase, 13);\n                assert.equal(buffer.lines.length, 23);\n                for (let i = 0; i < 10; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'ab');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'cd');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'ef');\n                assert.equal(buffer.lines.get(13)!.translateToString(), 'gh');\n                assert.equal(buffer.lines.get(14)!.translateToString(), 'ij');\n                assert.equal(buffer.lines.get(15)!.translateToString(), 'kl');\n                for (let i = 16; i < 23; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                const wrappedLines = [11, 13, 15];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n            describe('&& ydisp !== ybase', () => {\n              it('should keep ydisp at the same value', () => {\n                buffer.ydisp = 5;\n                buffer.resize(2, 10);\n                assert.equal(buffer.ydisp, 5);\n                assert.equal(buffer.ybase, 13);\n                assert.equal(buffer.lines.length, 23);\n                for (let i = 0; i < 10; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'ab');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'cd');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'ef');\n                assert.equal(buffer.lines.get(13)!.translateToString(), 'gh');\n                assert.equal(buffer.lines.get(14)!.translateToString(), 'ij');\n                assert.equal(buffer.lines.get(15)!.translateToString(), 'kl');\n                for (let i = 16; i < 23; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                const wrappedLines = [11, 13, 15];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n          });\n        });\n        describe('viewport filled, no scrollback remaining', () => {\n          // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported\n          describe('ybase !== 0', () => {\n            beforeEach(() => {\n              optionsService.options.scrollback = 10;\n              // Add 10 empty rows to start\n              for (let i = 0; i < 10; i++) {\n                buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));\n              }\n              buffer.ybase = 10;\n            });\n            describe('&& ydisp === ybase', () => {\n              it('should trim lines and keep ydisp = ybase', () => {\n                buffer.ydisp = 10;\n                buffer.y = 13;\n                buffer.resize(2, 10);\n                assert.equal(buffer.ydisp, 10);\n                assert.equal(buffer.ybase, 10);\n                assert.equal(buffer.lines.length, 20);\n                for (let i = 0; i < 7; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                assert.equal(buffer.lines.get(7)!.translateToString(), 'ab');\n                assert.equal(buffer.lines.get(8)!.translateToString(), 'cd');\n                assert.equal(buffer.lines.get(9)!.translateToString(), 'ef');\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'gh');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'ij');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'kl');\n                for (let i = 13; i < 20; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                const wrappedLines = [8, 10, 12];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n            describe('&& ydisp !== ybase', () => {\n              it('should trim lines and not change ydisp', () => {\n                buffer.ydisp = 5;\n                buffer.y = 13;\n                buffer.resize(2, 10);\n                assert.equal(buffer.ydisp, 5);\n                assert.equal(buffer.ybase, 10);\n                assert.equal(buffer.lines.length, 20);\n                for (let i = 0; i < 7; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                assert.equal(buffer.lines.get(7)!.translateToString(), 'ab');\n                assert.equal(buffer.lines.get(8)!.translateToString(), 'cd');\n                assert.equal(buffer.lines.get(9)!.translateToString(), 'ef');\n                assert.equal(buffer.lines.get(10)!.translateToString(), 'gh');\n                assert.equal(buffer.lines.get(11)!.translateToString(), 'ij');\n                assert.equal(buffer.lines.get(12)!.translateToString(), 'kl');\n                for (let i = 13; i < 20; i++) {\n                  assert.equal(buffer.lines.get(i)!.translateToString(), '  ');\n                }\n                const wrappedLines = [8, 10, 12];\n                for (let i = 0; i < buffer.lines.length; i++) {\n                  assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.includes(i), `line ${i} isWrapped must equal ${wrappedLines.includes(i)}`);\n                }\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n  describe('buffer marked to have no scrollback', () => {\n    it('should always have a scrollback of 0', () => {\n      // Test size on initialization\n      buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService, new MockLogService());\n      buffer.fillViewportRows();\n      assert.equal(buffer.lines.maxLength, INIT_ROWS);\n      // Test size on buffer increase\n      buffer.resize(INIT_COLS, INIT_ROWS * 2);\n      assert.equal(buffer.lines.maxLength, INIT_ROWS * 2);\n      // Test size on buffer decrease\n      buffer.resize(INIT_COLS, INIT_ROWS / 2);\n      assert.equal(buffer.lines.maxLength, INIT_ROWS / 2);\n    });\n  });\n\n  describe('addMarker', () => {\n    it('should adjust a marker line when the buffer is trimmed', () => {\n      buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());\n      buffer.fillViewportRows();\n      const marker = buffer.addMarker(buffer.lines.length - 1);\n      assert.equal(marker.line, buffer.lines.length - 1);\n      buffer.lines.onTrimEmitter.fire(1);\n      assert.equal(marker.line, buffer.lines.length - 2);\n    });\n    it('should dispose of a marker if it is trimmed off the buffer', () => {\n      buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());\n      buffer.fillViewportRows();\n      assert.equal(buffer.markers.length, 0);\n      const marker = buffer.addMarker(0);\n      assert.equal(marker.isDisposed, false);\n      assert.equal(buffer.markers.length, 1);\n      buffer.lines.onTrimEmitter.fire(1);\n      assert.equal(marker.isDisposed, true);\n      assert.equal(buffer.markers.length, 0);\n    });\n    it('should call onDispose', () => {\n      const eventStack: string[] = [];\n      buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());\n      buffer.fillViewportRows();\n      assert.equal(buffer.markers.length, 0);\n      const marker = buffer.addMarker(0);\n      marker.onDispose(() => eventStack.push('disposed'));\n      assert.equal(marker.isDisposed, false);\n      assert.equal(buffer.markers.length, 1);\n      buffer.lines.onTrimEmitter.fire(1);\n      assert.equal(marker.isDisposed, true);\n      assert.equal(buffer.markers.length, 0);\n      assert.deepEqual(eventStack, ['disposed']);\n    });\n  });\n\n  describe ('translateBufferLineToString', () => {\n    it('should handle selecting a section of ascii text', () => {\n      const line = new BufferLine(4);\n      line.setCell(0, createCellData(0, 'a', 1));\n      line.setCell(1, createCellData(0, 'b', 1));\n      line.setCell(2, createCellData(0, 'c', 1));\n      line.setCell(3, createCellData(0, 'd', 1));\n      buffer.lines.set(0, line);\n\n      const str = buffer.translateBufferLineToString(0, true, 0, 2);\n      assert.equal(str, 'ab');\n    });\n\n    it('should handle a cut-off double width character by including it', () => {\n      const line = new BufferLine(3);\n      line.setCell(0, createCellData(0, '語', 2));\n      line.setCell(1, createCellData(0, '', 0));\n      line.setCell(2, createCellData(0, 'a', 1));\n      buffer.lines.set(0, line);\n\n      const str1 = buffer.translateBufferLineToString(0, true, 0, 1);\n      assert.equal(str1, '語');\n    });\n\n    it('should handle a zero width character in the middle of the string by not including it', () => {\n      const line = new BufferLine(3);\n      line.setCell(0, createCellData(0, '語', 2));\n      line.setCell(1, createCellData(0, '', 0));\n      line.setCell(2, createCellData(0, 'a', 1));\n      buffer.lines.set(0, line);\n\n      const str0 = buffer.translateBufferLineToString(0, true, 0, 1);\n      assert.equal(str0, '語');\n\n      const str1 = buffer.translateBufferLineToString(0, true, 0, 2);\n      assert.equal(str1, '語');\n\n      const str2 = buffer.translateBufferLineToString(0, true, 0, 3);\n      assert.equal(str2, '語a');\n    });\n\n    it('should handle single width emojis', () => {\n      const line = new BufferLine(2);\n      line.setCell(0, createCellData(0, '😁', 1));\n      line.setCell(1, createCellData(0, 'a', 1));\n      buffer.lines.set(0, line);\n\n      const str1 = buffer.translateBufferLineToString(0, true, 0, 1);\n      assert.equal(str1, '😁');\n\n      const str2 = buffer.translateBufferLineToString(0, true, 0, 2);\n      assert.equal(str2, '😁a');\n    });\n\n    it('should handle double width emojis', () => {\n      const line = new BufferLine(2);\n      line.setCell(0, createCellData(0, '😁', 2));\n      line.setCell(1, createCellData(0, '', 0));\n      buffer.lines.set(0, line);\n\n      const str1 = buffer.translateBufferLineToString(0, true, 0, 1);\n      assert.equal(str1, '😁');\n\n      const str2 = buffer.translateBufferLineToString(0, true, 0, 2);\n      assert.equal(str2, '😁');\n\n      const line2 = new BufferLine(3);\n      line2.setCell(0, createCellData(0, '😁', 2));\n      line2.setCell(1, createCellData(0, '', 0));\n      line2.setCell(2, createCellData(0, 'a', 1));\n      buffer.lines.set(0, line2);\n\n      const str3 = buffer.translateBufferLineToString(0, true, 0, 3);\n      assert.equal(str3, '😁a');\n    });\n  });\n\n  describe('memory cleanup after shrinking', () => {\n    it('should realign memory from idle task execution', async () => {\n      buffer.fillViewportRows();\n\n      // shrink more than 2 times to trigger lazy memory cleanup\n      buffer.resize(INIT_COLS / 2 - 1, INIT_ROWS);\n\n      // sync\n      for (let i = 0; i < INIT_ROWS; i++) {\n        const line = buffer.lines.get(i)!;\n        // line memory is still at old size from initialization\n        assert.equal((line as any)._data.buffer.byteLength, INIT_COLS * 3 * 4);\n        // array.length and .length get immediately adjusted\n        assert.equal((line as any)._data.length, (INIT_COLS / 2 - 1) * 3);\n        assert.equal(line.length, INIT_COLS / 2 - 1);\n      }\n\n      // wait for a bit to give IdleTaskQueue a chance to kick in\n      // and finish memory cleaning\n      await new Promise(r => setTimeout(r, 30));\n\n      // cleanup should have realigned memory with exact bytelength\n      for (let i = 0; i < INIT_ROWS; i++) {\n        const line = buffer.lines.get(i)!;\n        assert.equal((line as any)._data.buffer.byteLength, (INIT_COLS / 2 - 1) * 3 * 4);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/buffer/Buffer.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CircularList, IInsertEvent } from 'common/CircularList';\nimport { IdleTaskQueue } from 'common/TaskQueue';\nimport { IAttributeData, IBufferLine, ICellData, ICharset } from 'common/Types';\nimport { ExtendedAttrs } from 'common/buffer/AttributeData';\nimport { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { getWrappedLineTrimmedLength, reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from 'common/buffer/BufferReflow';\nimport { CellData } from 'common/buffer/CellData';\nimport { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, WHITESPACE_CELL_WIDTH } from 'common/buffer/Constants';\nimport { Marker } from 'common/buffer/Marker';\nimport { IBuffer } from 'common/buffer/Types';\nimport { DEFAULT_CHARSET } from 'common/data/Charsets';\nimport { IBufferService, ILogService, IOptionsService } from 'common/services/Services';\n\nexport const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n *   - text content of this particular buffer\n *   - cursor position\n *   - scroll position\n */\nexport class Buffer implements IBuffer {\n  public lines: CircularList<IBufferLine>;\n  public ydisp: number = 0;\n  public ybase: number = 0;\n  public y: number = 0;\n  public x: number = 0;\n  public scrollBottom: number;\n  public scrollTop: number;\n  public tabs: { [column: number]: boolean | undefined } = {};\n  public savedY: number = 0;\n  public savedX: number = 0;\n  public savedCurAttrData = DEFAULT_ATTR_DATA.clone();\n  public savedCharset: ICharset | undefined = DEFAULT_CHARSET;\n  public savedCharsets: (ICharset | undefined)[] = [];\n  public savedGlevel: number = 0;\n  public savedOriginMode: boolean = false;\n  public savedWraparoundMode: boolean = true;\n  public markers: Marker[] = [];\n  private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n  private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);\n  private _cols: number;\n  private _rows: number;\n  private _isClearing: boolean = false;\n  private _memoryCleanupQueue: InstanceType<typeof IdleTaskQueue>;\n  private _memoryCleanupPosition = 0;\n\n  constructor(\n    private _hasScrollback: boolean,\n    private _optionsService: IOptionsService,\n    private _bufferService: IBufferService,\n    private readonly _logService: ILogService\n  ) {\n    this._cols = this._bufferService.cols;\n    this._rows = this._bufferService.rows;\n    this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._rows));\n    this.scrollTop = 0;\n    this.scrollBottom = this._rows - 1;\n    this.setupTabStops();\n    this._memoryCleanupQueue = new IdleTaskQueue(this._logService);\n  }\n\n  public getNullCell(attr?: IAttributeData): ICellData {\n    if (attr) {\n      this._nullCell.fg = attr.fg;\n      this._nullCell.bg = attr.bg;\n      this._nullCell.extended = attr.extended;\n    } else {\n      this._nullCell.fg = 0;\n      this._nullCell.bg = 0;\n      this._nullCell.extended = new ExtendedAttrs();\n    }\n    return this._nullCell;\n  }\n\n  public getWhitespaceCell(attr?: IAttributeData): ICellData {\n    if (attr) {\n      this._whitespaceCell.fg = attr.fg;\n      this._whitespaceCell.bg = attr.bg;\n      this._whitespaceCell.extended = attr.extended;\n    } else {\n      this._whitespaceCell.fg = 0;\n      this._whitespaceCell.bg = 0;\n      this._whitespaceCell.extended = new ExtendedAttrs();\n    }\n    return this._whitespaceCell;\n  }\n\n  public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {\n    return new BufferLine(this._bufferService.cols, this.getNullCell(attr), isWrapped);\n  }\n\n  public get hasScrollback(): boolean {\n    return this._hasScrollback && this.lines.maxLength > this._rows;\n  }\n\n  public get isCursorInViewport(): boolean {\n    const absoluteY = this.ybase + this.y;\n    const relativeY = absoluteY - this.ydisp;\n    return (relativeY >= 0 && relativeY < this._rows);\n  }\n\n  /**\n   * Gets the correct buffer length based on the rows provided, the terminal's\n   * scrollback and whether this buffer is flagged to have scrollback or not.\n   * @param rows The terminal rows to use in the calculation.\n   */\n  private _getCorrectBufferLength(rows: number): number {\n    if (!this._hasScrollback) {\n      return rows;\n    }\n\n    const correctBufferLength = rows + this._optionsService.rawOptions.scrollback;\n\n    return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength;\n  }\n\n  /**\n   * Fills the buffer's viewport with blank lines.\n   */\n  public fillViewportRows(fillAttr?: IAttributeData): void {\n    if (this.lines.length === 0) {\n      fillAttr ??= DEFAULT_ATTR_DATA;\n      let i = this._rows;\n      while (i--) {\n        this.lines.push(this.getBlankLine(fillAttr));\n      }\n    }\n  }\n\n  /**\n   * Clears the buffer to it's initial state, discarding all previous data.\n   */\n  public clear(): void {\n    this.ydisp = 0;\n    this.ybase = 0;\n    this.y = 0;\n    this.x = 0;\n    this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._rows));\n    this.scrollTop = 0;\n    this.scrollBottom = this._rows - 1;\n    this.setupTabStops();\n  }\n\n  /**\n   * Resizes the buffer, adjusting its data accordingly.\n   * @param newCols The new number of columns.\n   * @param newRows The new number of rows.\n   */\n  public resize(newCols: number, newRows: number): void {\n    // store reference to null cell with default attrs\n    const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);\n\n    // count bufferlines with overly big memory to be cleaned afterwards\n    let dirtyMemoryLines = 0;\n\n    // Increase max length if needed before adjustments to allow space to fill\n    // as required.\n    const newMaxLength = this._getCorrectBufferLength(newRows);\n    if (newMaxLength > this.lines.maxLength) {\n      this.lines.maxLength = newMaxLength;\n    }\n\n    // if (this._cols > newCols) {\n    //   console.log('increase!');\n    // }\n\n    // The following adjustments should only happen if the buffer has been\n    // initialized/filled.\n    if (this.lines.length > 0) {\n      // Deal with columns increasing (reducing needs to happen after reflow)\n      if (this._cols < newCols) {\n        for (let i = 0; i < this.lines.length; i++) {\n          // +boolean for fast 0 or 1 conversion\n          dirtyMemoryLines += +this.lines.get(i)!.resize(newCols, nullCell);\n        }\n      }\n\n      // Resize rows in both directions as needed\n      let addToY = 0;\n      if (this._rows < newRows) {\n        for (let y = this._rows; y < newRows; y++) {\n          if (this.lines.length < newRows + this.ybase) {\n            if (this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined) {\n              // Just add the new missing rows on Windows as conpty reprints the screen with it's\n              // view of the world. Once a line enters scrollback for conpty it remains there\n              this.lines.push(new BufferLine(newCols, nullCell));\n            } else {\n              if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {\n                // There is room above the buffer and there are no empty elements below the line,\n                // scroll up\n                this.ybase--;\n                addToY++;\n                if (this.ydisp > 0) {\n                  // Viewport is at the top of the buffer, must increase downwards\n                  this.ydisp--;\n                }\n              } else {\n                // Add a blank line if there is no buffer left at the top to scroll to, or if there\n                // are blank lines after the cursor\n                this.lines.push(new BufferLine(newCols, nullCell));\n              }\n            }\n          }\n        }\n      } else { // (this._rows >= newRows)\n        for (let y = this._rows; y > newRows; y--) {\n          if (this.lines.length > newRows + this.ybase) {\n            if (this.lines.length > this.ybase + this.y + 1) {\n              // The line is a blank line below the cursor, remove it\n              this.lines.pop();\n            } else {\n              // The line is the cursor, scroll down\n              this.ybase++;\n              this.ydisp++;\n            }\n          }\n        }\n      }\n\n      // Reduce max length if needed after adjustments, this is done after as it\n      // would otherwise cut data from the bottom of the buffer.\n      if (newMaxLength < this.lines.maxLength) {\n        // Trim from the top of the buffer and adjust ybase and ydisp.\n        const amountToTrim = this.lines.length - newMaxLength;\n        if (amountToTrim > 0) {\n          this.lines.trimStart(amountToTrim);\n          this.ybase = Math.max(this.ybase - amountToTrim, 0);\n          this.ydisp = Math.max(this.ydisp - amountToTrim, 0);\n          this.savedY = Math.max(this.savedY - amountToTrim, 0);\n        }\n        this.lines.maxLength = newMaxLength;\n      }\n\n      // Make sure that the cursor stays on screen\n      this.x = Math.min(this.x, newCols - 1);\n      this.y = Math.min(this.y, newRows - 1);\n      if (addToY) {\n        this.y += addToY;\n      }\n      this.savedX = Math.min(this.savedX, newCols - 1);\n\n      this.scrollTop = 0;\n    }\n\n    this.scrollBottom = newRows - 1;\n\n    if (this._isReflowEnabled) {\n      this._reflow(newCols, newRows);\n\n      // Trim the end of the line off if cols shrunk\n      if (this._cols > newCols) {\n        for (let i = 0; i < this.lines.length; i++) {\n          // +boolean for fast 0 or 1 conversion\n          dirtyMemoryLines += +this.lines.get(i)!.resize(newCols, nullCell);\n        }\n      }\n    }\n\n    this._cols = newCols;\n    this._rows = newRows;\n\n    // Ensure the cursor position invariant: ybase + y must be within buffer bounds\n    // This can be violated during reflow or when shrinking rows\n    if (this.lines.length > 0) {\n      const maxY = Math.max(0, this.lines.length - this.ybase - 1);\n      this.y = Math.min(this.y, maxY);\n    }\n\n    this._memoryCleanupQueue.clear();\n    // schedule memory cleanup only, if more than 10% of the lines are affected\n    if (dirtyMemoryLines > 0.1 * this.lines.length) {\n      this._memoryCleanupPosition = 0;\n      this._memoryCleanupQueue.enqueue(() => this._batchedMemoryCleanup());\n    }\n  }\n\n  private _batchedMemoryCleanup(): boolean {\n    let normalRun = true;\n    if (this._memoryCleanupPosition >= this.lines.length) {\n      // cleanup made it once through all lines, thus rescan in loop below to also catch shifted\n      // lines, which should finish rather quick if there are no more cleanups pending\n      this._memoryCleanupPosition = 0;\n      normalRun = false;\n    }\n    let counted = 0;\n    while (this._memoryCleanupPosition < this.lines.length) {\n      counted += this.lines.get(this._memoryCleanupPosition++)!.cleanupMemory();\n      // cleanup max 100 lines per batch\n      if (counted > 100) {\n        return true;\n      }\n    }\n    // normal runs always need another rescan afterwards\n    // if we made it here with normalRun=false, we are in a final run\n    // and can end the cleanup task for sure\n    return normalRun;\n  }\n\n  private get _isReflowEnabled(): boolean {\n    const windowsPty = this._optionsService.rawOptions.windowsPty;\n    if (windowsPty && windowsPty.buildNumber) {\n      return this._hasScrollback && windowsPty.backend === 'conpty' && windowsPty.buildNumber >= 21376;\n    }\n    return this._hasScrollback;\n  }\n\n  private _reflow(newCols: number, newRows: number): void {\n    if (this._cols === newCols) {\n      return;\n    }\n\n    // Iterate through rows, ignore the last one as it cannot be wrapped\n    if (newCols > this._cols) {\n      this._reflowLarger(newCols, newRows);\n    } else {\n      this._reflowSmaller(newCols, newRows);\n    }\n  }\n\n  private _reflowLarger(newCols: number, newRows: number): void {\n    const reflowCursorLine = this._optionsService.rawOptions.reflowCursorLine;\n    const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA), reflowCursorLine);\n    if (toRemove.length > 0) {\n      const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);\n      reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);\n      this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);\n    }\n  }\n\n  private _reflowLargerAdjustViewport(newCols: number, newRows: number, countRemoved: number): void {\n    const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);\n    // Adjust viewport based on number of items removed\n    let viewportAdjustments = countRemoved;\n    while (viewportAdjustments-- > 0) {\n      if (this.ybase === 0) {\n        if (this.y > 0) {\n          this.y--;\n        }\n        if (this.lines.length < newRows) {\n          // Add an extra row at the bottom of the viewport\n          this.lines.push(new BufferLine(newCols, nullCell));\n        }\n      } else {\n        if (this.ydisp === this.ybase) {\n          this.ydisp--;\n        }\n        this.ybase--;\n      }\n    }\n    this.savedY = Math.max(this.savedY - countRemoved, 0);\n  }\n\n  private _reflowSmaller(newCols: number, newRows: number): void {\n    const reflowCursorLine = this._optionsService.rawOptions.reflowCursorLine;\n    const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);\n    // Gather all BufferLines that need to be inserted into the Buffer here so that they can be\n    // batched up and only committed once\n    const toInsert = [];\n    let countToInsert = 0;\n    // Go backwards as many lines may be trimmed and this will avoid considering them\n    for (let y = this.lines.length - 1; y >= 0; y--) {\n      // Check whether this line is a problem\n      let nextLine = this.lines.get(y) as BufferLine;\n      if (!nextLine || !nextLine.isWrapped && nextLine.getTrimmedLength() <= newCols) {\n        continue;\n      }\n\n      // Gather wrapped lines and adjust y to be the starting line\n      const wrappedLines: BufferLine[] = [nextLine];\n      while (nextLine.isWrapped && y > 0) {\n        nextLine = this.lines.get(--y) as BufferLine;\n        wrappedLines.unshift(nextLine);\n      }\n\n      if (!reflowCursorLine) {\n        // If these lines contain the cursor don't touch them, the program will handle fixing up\n        // wrapped lines with the cursor\n        const absoluteY = this.ybase + this.y;\n        if (absoluteY >= y && absoluteY < y + wrappedLines.length) {\n          continue;\n        }\n      }\n\n      const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();\n      const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);\n      const linesToAdd = destLineLengths.length - wrappedLines.length;\n      let trimmedLines: number;\n      if (this.ybase === 0 && this.y !== this.lines.length - 1) {\n        // If the top section of the buffer is not yet filled\n        trimmedLines = Math.max(0, this.y - this.lines.maxLength + linesToAdd);\n      } else {\n        trimmedLines = Math.max(0, this.lines.length - this.lines.maxLength + linesToAdd);\n      }\n\n      // Add the new lines\n      const newLines: BufferLine[] = [];\n      for (let i = 0; i < linesToAdd; i++) {\n        const newLine = this.getBlankLine(DEFAULT_ATTR_DATA, true) as BufferLine;\n        newLines.push(newLine);\n      }\n      if (newLines.length > 0) {\n        toInsert.push({\n          // countToInsert here gets the actual index, taking into account other inserted items.\n          // using this we can iterate through the list forwards\n          start: y + wrappedLines.length + countToInsert,\n          newLines\n        });\n        countToInsert += newLines.length;\n      }\n      wrappedLines.push(...newLines);\n\n      // Copy buffer data to new locations, this needs to happen backwards to do in-place\n      let destLineIndex = destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);\n      let destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;\n      if (destCol === 0) {\n        destLineIndex--;\n        destCol = destLineLengths[destLineIndex];\n      }\n      let srcLineIndex = wrappedLines.length - linesToAdd - 1;\n      let srcCol = lastLineLength;\n      while (srcLineIndex >= 0) {\n        const cellsToCopy = Math.min(srcCol, destCol);\n        if (wrappedLines[destLineIndex] === undefined) {\n          // Sanity check that the line exists, this has been known to fail for an unknown reason\n          // which would stop the reflow from happening if an exception would throw.\n          break;\n        }\n        wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy, true);\n        destCol -= cellsToCopy;\n        if (destCol === 0) {\n          destLineIndex--;\n          destCol = destLineLengths[destLineIndex];\n        }\n        srcCol -= cellsToCopy;\n        if (srcCol === 0) {\n          srcLineIndex--;\n          const wrappedLinesIndex = Math.max(srcLineIndex, 0);\n          srcCol = getWrappedLineTrimmedLength(wrappedLines, wrappedLinesIndex, this._cols);\n        }\n      }\n\n      // Null out the end of the line ends if a wide character wrapped to the following line\n      for (let i = 0; i < wrappedLines.length; i++) {\n        if (destLineLengths[i] < newCols) {\n          wrappedLines[i].setCell(destLineLengths[i], nullCell);\n        }\n      }\n\n      // Adjust viewport as needed\n      let viewportAdjustments = linesToAdd - trimmedLines;\n      while (viewportAdjustments-- > 0) {\n        if (this.ybase === 0) {\n          if (this.y < newRows - 1) {\n            this.y++;\n            this.lines.pop();\n          } else {\n            this.ybase++;\n            this.ydisp++;\n          }\n        } else {\n          // Ensure ybase does not exceed its maximum value\n          if (this.ybase < Math.min(this.lines.maxLength, this.lines.length + countToInsert) - newRows) {\n            if (this.ybase === this.ydisp) {\n              this.ydisp++;\n            }\n            this.ybase++;\n          }\n        }\n      }\n      this.savedY = Math.min(this.savedY + linesToAdd, this.ybase + newRows - 1);\n    }\n\n    // Rearrange lines in the buffer if there are any insertions, this is done at the end rather\n    // than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many\n    // costly calls to CircularList.splice.\n    if (toInsert.length > 0) {\n      // Record buffer insert events and then play them back backwards so that the indexes are\n      // correct\n      const insertEvents: IInsertEvent[] = [];\n\n      // Record original lines so they don't get overridden when we rearrange the list\n      const originalLines: BufferLine[] = [];\n      for (let i = 0; i < this.lines.length; i++) {\n        originalLines.push(this.lines.get(i) as BufferLine);\n      }\n      const originalLinesLength = this.lines.length;\n\n      let originalLineIndex = originalLinesLength - 1;\n      let nextToInsertIndex = 0;\n      let nextToInsert = toInsert[nextToInsertIndex];\n      this.lines.length = Math.min(this.lines.maxLength, this.lines.length + countToInsert);\n      let countInsertedSoFar = 0;\n      for (let i = Math.min(this.lines.maxLength - 1, originalLinesLength + countToInsert - 1); i >= 0; i--) {\n        if (nextToInsert && nextToInsert.start > originalLineIndex + countInsertedSoFar) {\n          // Insert extra lines here, adjusting i as needed\n          for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) {\n            this.lines.set(i--, nextToInsert.newLines[nextI]);\n          }\n          i++;\n\n          // Create insert events for later\n          insertEvents.push({\n            index: originalLineIndex + 1,\n            amount: nextToInsert.newLines.length\n          });\n\n          countInsertedSoFar += nextToInsert.newLines.length;\n          nextToInsert = toInsert[++nextToInsertIndex];\n        } else {\n          this.lines.set(i, originalLines[originalLineIndex--]);\n        }\n      }\n\n      // Update markers\n      let insertCountEmitted = 0;\n      for (let i = insertEvents.length - 1; i >= 0; i--) {\n        insertEvents[i].index += insertCountEmitted;\n        this.lines.onInsertEmitter.fire(insertEvents[i]);\n        insertCountEmitted += insertEvents[i].amount;\n      }\n      const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength);\n      if (amountToTrim > 0) {\n        this.lines.onTrimEmitter.fire(amountToTrim);\n      }\n    }\n  }\n\n  /**\n   * Translates a buffer line to a string, with optional start and end columns.\n   * Wide characters will count as two columns in the resulting string. This\n   * function is useful for getting the actual text underneath the raw selection\n   * position.\n   * @param lineIndex The absolute index of the line being translated.\n   * @param trimRight Whether to trim whitespace to the right.\n   * @param startCol The column to start at.\n   * @param endCol The column to end at.\n   */\n  public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol?: number): string {\n    const line = this.lines.get(lineIndex);\n    if (!line) {\n      return '';\n    }\n    return line.translateToString(trimRight, startCol, endCol);\n  }\n\n  public getWrappedRangeForLine(y: number): { first: number, last: number } {\n    let first = y;\n    let last = y;\n    // Scan upwards for wrapped lines\n    while (first > 0 && this.lines.get(first)!.isWrapped) {\n      first--;\n    }\n    // Scan downwards for wrapped lines\n    while (last + 1 < this.lines.length && this.lines.get(last + 1)!.isWrapped) {\n      last++;\n    }\n    return { first, last };\n  }\n\n  /**\n   * Setup the tab stops.\n   * @param i The index to start setting up tab stops from.\n   */\n  public setupTabStops(i?: number): void {\n    if (i !== null && i !== undefined) {\n      if (!this.tabs[i]) {\n        i = this.prevStop(i);\n      }\n    } else {\n      this.tabs = {};\n      i = 0;\n    }\n\n    for (; i < this._cols; i += this._optionsService.rawOptions.tabStopWidth) {\n      this.tabs[i] = true;\n    }\n  }\n\n  /**\n   * Move the cursor to the previous tab stop from the given position (default is current).\n   * @param x The position to move the cursor to the previous tab stop.\n   */\n  public prevStop(x?: number): number {\n    x ??= this.x;\n    while (!this.tabs[--x] && x > 0);\n    return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;\n  }\n\n  /**\n   * Move the cursor one tab stop forward from the given position (default is current).\n   * @param x The position to move the cursor one tab stop forward.\n   */\n  public nextStop(x?: number): number {\n    x ??= this.x;\n    while (!this.tabs[++x] && x < this._cols);\n    return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;\n  }\n\n  /**\n   * Clears markers on single line.\n   * @param y The line to clear.\n   */\n  public clearMarkers(y: number): void {\n    this._isClearing = true;\n    for (let i = 0; i < this.markers.length; i++) {\n      if (this.markers[i].line === y) {\n        this.markers[i].dispose();\n        this.markers.splice(i--, 1);\n      }\n    }\n    this._isClearing = false;\n  }\n\n  /**\n   * Clears markers on all lines\n   */\n  public clearAllMarkers(): void {\n    this._isClearing = true;\n    for (let i = 0; i < this.markers.length; i++) {\n      this.markers[i].dispose();\n    }\n    this.markers.length = 0;\n    this._isClearing = false;\n  }\n\n  public addMarker(y: number): Marker {\n    const marker = new Marker(y);\n    this.markers.push(marker);\n    marker.register(this.lines.onTrim(amount => {\n      marker.line -= amount;\n      // The marker should be disposed when the line is trimmed from the buffer\n      if (marker.line < 0) {\n        marker.dispose();\n      }\n    }));\n    marker.register(this.lines.onInsert(event => {\n      if (marker.line >= event.index) {\n        marker.line += event.amount;\n      }\n    }));\n    marker.register(this.lines.onDelete(event => {\n      // Delete the marker if it's within the range\n      if (marker.line >= event.index && marker.line < event.index + event.amount) {\n        marker.dispose();\n      }\n\n      // Shift the marker if it's after the deleted range\n      if (marker.line > event.index) {\n        marker.line -= event.amount;\n      }\n    }));\n    marker.register(marker.onDispose(() => this._removeMarker(marker)));\n    return marker;\n  }\n\n  private _removeMarker(marker: Marker): void {\n    if (!this._isClearing) {\n      this.markers.splice(this.markers.indexOf(marker), 1);\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/buffer/BufferLine.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content, UnderlineStyle, BgFlags, Attributes, FgFlags } from 'common/buffer/Constants';\nimport { BufferLine } from 'common/buffer//BufferLine';\nimport { CellData } from 'common/buffer/CellData';\nimport { CharData, IBufferLine, IExtendedAttrs } from '../Types';\nimport { assert } from 'chai';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { createCellData, NULL_CELL_DATA } from 'common/TestUtils.test';\n\n\nclass TestBufferLine extends BufferLine {\n  public get combined(): {[index: number]: string} {\n    return this._combined;\n  }\n\n  public toArray(): CharData[] {\n    const result = [];\n    for (let i = 0; i < this.length; ++i) {\n      result.push(this.loadCell(i, new CellData()).getAsCharData());\n    }\n    return result;\n  }\n}\n\nfunction extendedAttributes(line: IBufferLine, index: number): IExtendedAttrs | undefined {\n  const cell = new CellData();\n  line.loadCell(index, cell);\n  return cell.hasExtendedAttrs() !== 0 ? cell.extended : undefined;\n}\n\ndescribe('AttributeData', () => {\n  describe('extended attributes', () => {\n    it('hasExtendedAttrs', () => {\n      const attrs = new AttributeData();\n      assert.equal(!!attrs.hasExtendedAttrs(), false);\n      attrs.bg |= BgFlags.HAS_EXTENDED;\n      assert.equal(!!attrs.hasExtendedAttrs(), true);\n    });\n    it('getUnderlineColor - P256', () => {\n      const attrs = new AttributeData();\n      // set a P256 color\n      attrs.extended.underlineColor = Attributes.CM_P256 | 45;\n\n      // should use FG color if BgFlags.HAS_EXTENDED is not set\n      assert.equal(attrs.getUnderlineColor(), -1);\n\n      // should use underlineColor if BgFlags.HAS_EXTENDED is set and underlineColor holds a value\n      attrs.bg |= BgFlags.HAS_EXTENDED;\n      assert.equal(attrs.getUnderlineColor(), 45);\n\n      // should use FG color if underlineColor holds no value\n      attrs.extended.underlineColor = 0;\n      attrs.fg |= Attributes.CM_P256 | 123;\n      assert.equal(attrs.getUnderlineColor(), 123);\n    });\n    it('getUnderlineColor - RGB', () => {\n      const attrs = new AttributeData();\n      // set a P256 color\n      attrs.extended.underlineColor = Attributes.CM_RGB | (1 << 16) | (2 << 8) | 3;\n\n      // should use FG color if BgFlags.HAS_EXTENDED is not set\n      assert.equal(attrs.getUnderlineColor(), -1);\n\n      // should use underlineColor if BgFlags.HAS_EXTENDED is set and underlineColor holds a value\n      attrs.bg |= BgFlags.HAS_EXTENDED;\n      assert.equal(attrs.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);\n\n      // should use FG color if underlineColor holds no value\n      attrs.extended.underlineColor = 0;\n      attrs.fg |= Attributes.CM_P256 | 123;\n      assert.equal(attrs.getUnderlineColor(), 123);\n    });\n    it('getUnderlineColorMode / isUnderlineColorRGB / isUnderlineColorPalette / isUnderlineColorDefault', () => {\n      const attrs = new AttributeData();\n\n      // should always return color mode of fg\n      for (const mode of [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]) {\n        attrs.extended.underlineColor = mode;\n        assert.equal(attrs.getUnderlineColorMode(), attrs.getFgColorMode());\n        assert.equal(attrs.isUnderlineColorDefault(), true);\n      }\n      attrs.fg = Attributes.CM_RGB;\n      for (const mode of [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]) {\n        attrs.extended.underlineColor = mode;\n        assert.equal(attrs.getUnderlineColorMode(), attrs.getFgColorMode());\n        assert.equal(attrs.isUnderlineColorDefault(), false);\n        assert.equal(attrs.isUnderlineColorRGB(), true);\n      }\n\n      // should return own mode\n      attrs.bg |= BgFlags.HAS_EXTENDED;\n      attrs.extended.underlineColor = Attributes.CM_DEFAULT;\n      assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_DEFAULT);\n      attrs.extended.underlineColor = Attributes.CM_P16;\n      assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_P16);\n      assert.equal(attrs.isUnderlineColorPalette(), true);\n      attrs.extended.underlineColor = Attributes.CM_P256;\n      assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_P256);\n      assert.equal(attrs.isUnderlineColorPalette(), true);\n      attrs.extended.underlineColor = Attributes.CM_RGB;\n      assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_RGB);\n      assert.equal(attrs.isUnderlineColorRGB(), true);\n    });\n    it('getUnderlineStyle', () => {\n      const attrs = new AttributeData();\n\n      // defaults to no underline style\n      assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);\n\n      // should return NONE if UNDERLINE is not set\n      attrs.extended.underlineStyle = UnderlineStyle.CURLY;\n      assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);\n\n      // should return SINGLE style if UNDERLINE is set and HAS_EXTENDED is false\n      attrs.fg |= FgFlags.UNDERLINE;\n      assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.SINGLE);\n\n      // should return correct style if both is set\n      attrs.bg |= BgFlags.HAS_EXTENDED;\n      assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.CURLY);\n\n      // should return NONE if UNDERLINE is not set, but HAS_EXTENDED is true\n      attrs.fg &= ~FgFlags.UNDERLINE;\n      assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);\n    });\n    it('getUnderlineVariantOffset', () => {\n      const attrs = new AttributeData();\n\n      // defaults to no offset\n      assert.equal(attrs.getUnderlineVariantOffset(), 0);\n\n      // should return 0 - 7\n      for (let i = 0; i < 8; ++i) {\n        attrs.extended.underlineVariantOffset = i;\n        assert.equal(attrs.getUnderlineVariantOffset(), i);\n      }\n    });\n  });\n});\n\ndescribe('CellData', () => {\n  it('CharData <--> CellData equality', () => {\n    const cell = new CellData();\n    // ASCII\n    cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]);\n    assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);\n    assert.equal(cell.isCombined(), 0);\n    // combining\n    cell.setFromCharData([123, 'e\\u0301', 1, '\\u0301'.charCodeAt(0)]);\n    assert.deepEqual(cell.getAsCharData(), [123, 'e\\u0301', 1, '\\u0301'.charCodeAt(0)]);\n    assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);\n    // surrogate\n    cell.setFromCharData([123, '𝄞', 1, 0x1D11E]);\n    assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);\n    assert.equal(cell.isCombined(), 0);\n    // surrogate + combining\n    cell.setFromCharData([123, '𓂀\\u0301', 1, '𓂀\\u0301'.charCodeAt(2)]);\n    assert.deepEqual(cell.getAsCharData(), [123, '𓂀\\u0301', 1, '𓂀\\u0301'.charCodeAt(2)]);\n    assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);\n    // wide char\n    cell.setFromCharData([123, '１', 2, '１'.charCodeAt(0)]);\n    assert.deepEqual(cell.getAsCharData(), [123, '１', 2, '１'.charCodeAt(0)]);\n    assert.equal(cell.isCombined(), 0);\n  });\n});\n\ndescribe('BufferLine', function(): void {\n  it('ctor', function(): void {\n    let line: IBufferLine = new TestBufferLine(0);\n    assert.equal(line.length, 0);\n    assert.equal(line.isWrapped, false);\n    line = new TestBufferLine(10);\n    assert.equal(line.length, 10);\n    assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n    assert.equal(line.isWrapped, false);\n    line = new TestBufferLine(10, undefined, true);\n    assert.equal(line.length, 10);\n    assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n    assert.equal(line.isWrapped, true);\n    line = new TestBufferLine(10, createCellData(123, 'a', 456), true);\n    assert.equal(line.length, 10);\n    assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [123, 'a', 456, 'a'.charCodeAt(0)]);\n    assert.equal(line.isWrapped, true);\n  });\n  it('insertCells', function(): void {\n    const line = new TestBufferLine(3);\n    line.setCell(0, createCellData(1, 'a', 1));\n    line.setCell(1, createCellData(2, 'b', 1));\n    line.setCell(2, createCellData(3, 'c', 1));\n    line.insertCells(1, 3, createCellData(4, 'd', 1));\n    assert.deepEqual(line.toArray(), [\n      [1, 'a', 1, 'a'.charCodeAt(0)],\n      [4, 'd', 1, 'd'.charCodeAt(0)],\n      [4, 'd', 1, 'd'.charCodeAt(0)]\n    ]);\n  });\n  it('deleteCells', function(): void {\n    const line = new TestBufferLine(5);\n    line.setCell(0, createCellData(1, 'a', 1));\n    line.setCell(1, createCellData(2, 'b', 1));\n    line.setCell(2, createCellData(3, 'c', 1));\n    line.setCell(3, createCellData(4, 'd', 1));\n    line.setCell(4, createCellData(5, 'e', 1));\n    line.deleteCells(1, 2, createCellData(6, 'f', 1));\n    assert.deepEqual(line.toArray(), [\n      [1, 'a', 1, 'a'.charCodeAt(0)],\n      [4, 'd', 1, 'd'.charCodeAt(0)],\n      [5, 'e', 1, 'e'.charCodeAt(0)],\n      [6, 'f', 1, 'f'.charCodeAt(0)],\n      [6, 'f', 1, 'f'.charCodeAt(0)]\n    ]);\n  });\n  it('replaceCells', function(): void {\n    const line = new TestBufferLine(5);\n    line.setCell(0, createCellData(1, 'a', 1));\n    line.setCell(1, createCellData(2, 'b', 1));\n    line.setCell(2, createCellData(3, 'c', 1));\n    line.setCell(3, createCellData(4, 'd', 1));\n    line.setCell(4, createCellData(5, 'e', 1));\n    line.replaceCells(2, 4, createCellData(6, 'f', 1));\n    assert.deepEqual(line.toArray(), [\n      [1, 'a', 1, 'a'.charCodeAt(0)],\n      [2, 'b', 1, 'b'.charCodeAt(0)],\n      [6, 'f', 1, 'f'.charCodeAt(0)],\n      [6, 'f', 1, 'f'.charCodeAt(0)],\n      [5, 'e', 1, 'e'.charCodeAt(0)]\n    ]);\n  });\n  it('fill', function(): void {\n    const line = new TestBufferLine(5);\n    line.setCell(0, createCellData(1, 'a', 1));\n    line.setCell(1, createCellData(2, 'b', 1));\n    line.setCell(2, createCellData(3, 'c', 1));\n    line.setCell(3, createCellData(4, 'd', 1));\n    line.setCell(4, createCellData(5, 'e', 1));\n    line.fill(createCellData(123, 'z', 1));\n    assert.deepEqual(line.toArray(), [\n      [123, 'z', 1, 'z'.charCodeAt(0)],\n      [123, 'z', 1, 'z'.charCodeAt(0)],\n      [123, 'z', 1, 'z'.charCodeAt(0)],\n      [123, 'z', 1, 'z'.charCodeAt(0)],\n      [123, 'z', 1, 'z'.charCodeAt(0)]\n    ]);\n  });\n  it('clone', function(): void {\n    const line = new TestBufferLine(5, undefined, true);\n    line.setCell(0, createCellData(1, 'a', 1));\n    line.setCell(1, createCellData(2, 'b', 1));\n    line.setCell(2, createCellData(3, 'c', 1));\n    line.setCell(3, createCellData(4, 'd', 1));\n    line.setCell(4, createCellData(5, 'e', 1));\n    const line2 = line.clone();\n    assert.deepEqual(TestBufferLine.prototype.toArray.apply(line2), line.toArray());\n    assert.equal(line2.length, line.length);\n    assert.equal(line2.isWrapped, line.isWrapped);\n  });\n  it('copyFrom', function(): void {\n    const line = new TestBufferLine(5);\n    line.setCell(0, createCellData(1, 'a', 1));\n    line.setCell(1, createCellData(2, 'b', 1));\n    line.setCell(2, createCellData(3, 'c', 1));\n    line.setCell(3, createCellData(4, 'd', 1));\n    line.setCell(4, createCellData(5, 'e', 1));\n    const line2 = new TestBufferLine(5, createCellData(1, 'a', 1), true);\n    line2.copyFrom(line);\n    assert.deepEqual(line2.toArray(), line.toArray());\n    assert.equal(line2.length, line.length);\n    assert.equal(line2.isWrapped, line.isWrapped);\n  });\n  it('should support combining chars', function(): void {\n    // CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print\n    // --> set code to the last charCodeAt value of the string\n    // Note: needs to be fixed once the string pointer is in place\n    const line = new TestBufferLine(2, createCellData(1, 'e\\u0301', 1));\n    assert.deepEqual(line.toArray(), [[1, 'e\\u0301', 1, '\\u0301'.charCodeAt(0)], [1, 'e\\u0301', 1, '\\u0301'.charCodeAt(0)]]);\n    const line2 = new TestBufferLine(5, createCellData(1, 'a', 1), true);\n    line2.copyFrom(line);\n    assert.deepEqual(line2.toArray(), line.toArray());\n    const line3 = line.clone();\n    assert.deepEqual(TestBufferLine.prototype.toArray.apply(line3), line.toArray());\n  });\n  describe('resize', function(): void {\n    it('enlarge(false)', function(): void {\n      const line = new TestBufferLine(5, createCellData(1, 'a', 1), false);\n      line.resize(10, createCellData(1, 'a', 1));\n      assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)]));\n    });\n    it('enlarge(true)', function(): void {\n      const line = new TestBufferLine(5, createCellData(1, 'a', 1), false);\n      line.resize(10, createCellData(1, 'a', 1));\n      assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)]));\n    });\n    it('shrink(true) - should apply new size', function(): void {\n      const line = new TestBufferLine(10, createCellData(1, 'a', 1), false);\n      line.resize(5, createCellData(1, 'a', 1));\n      assert.deepEqual(line.toArray(), (Array(5) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)]));\n    });\n    it('shrink to 0 length', function(): void {\n      const line = new TestBufferLine(10, createCellData(1, 'a', 1), false);\n      line.resize(0, createCellData(1, 'a', 1));\n      assert.deepEqual(line.toArray(), (Array(0) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)]));\n    });\n    it('should remove combining data on replaced cells after shrinking then enlarging', () => {\n      const line = new TestBufferLine(10, createCellData(1, 'a', 1), false);\n      line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);\n      line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);\n      assert.equal(line.translateToString(), 'aa😁aaaaaa😁');\n      assert.equal(Object.keys(line.combined).length, 2);\n      line.resize(5, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aa😁aa');\n      line.resize(10, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aa😁aaaaaaa');\n      assert.equal(Object.keys(line.combined).length, 1);\n    });\n  });\n  describe('getTrimLength', function(): void {\n    it('empty line', function(): void {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      assert.equal(line.getTrimmedLength(), 0);\n    });\n    it('ASCII', function(): void {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, 'a', 1));\n      assert.equal(line.getTrimmedLength(), 3);\n    });\n    it('surrogate', function(): void {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, '𝄞', 1));\n      assert.equal(line.getTrimmedLength(), 3);\n    });\n    it('combining', function(): void {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, 'e\\u0301', 1));\n      assert.equal(line.getTrimmedLength(), 3);\n    });\n    it('fullwidth', function(): void {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, '１', 2));\n      line.setCell(3, createCellData(0, '', 0));\n      assert.equal(line.getTrimmedLength(), 4); // also counts null cell after fullwidth\n    });\n  });\n  describe('translateToString with and w\\'o trimming', function(): void {\n    it('empty line', function(): void {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      const columns: number[] = [];\n      assert.equal(line.translateToString(false, undefined, undefined, columns), '          ');\n      assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), '');\n      assert.deepEqual(columns, [0]);\n    });\n    it('ASCII', function(): void {\n      const columns: number[] = [];\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, 'a', 1));\n      line.setCell(4, createCellData(1, 'a', 1));\n      line.setCell(5, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(false, undefined, undefined, columns), 'a a aa    ');\n      assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), 'a a aa');\n      assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6]);\n      for (const trimRight of [true, false]) {\n        assert.equal(line.translateToString(trimRight, 0, 5, columns), 'a a a');\n        assert.deepEqual(columns, [0, 1, 2, 3, 4, 5]);\n        assert.equal(line.translateToString(trimRight, 0, 4, columns), 'a a ');\n        assert.deepEqual(columns, [0, 1, 2, 3, 4]);\n        assert.equal(line.translateToString(trimRight, 0, 3, columns), 'a a');\n        assert.deepEqual(columns, [0, 1, 2, 3]);\n      }\n\n    });\n    it('surrogate', function(): void {\n      const columns: number[] = [];\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, '𝄞', 1));\n      line.setCell(4, createCellData(1, '𝄞', 1));\n      line.setCell(5, createCellData(1, '𝄞', 1));\n      assert.equal(line.translateToString(false, undefined, undefined, columns), 'a 𝄞 𝄞𝄞    ');\n      assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), 'a 𝄞 𝄞𝄞');\n      assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5, 5, 6]);\n      for (const trimRight of [true, false]) {\n        assert.equal(line.translateToString(trimRight, 0, 5, columns), 'a 𝄞 𝄞');\n        assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5]);\n        assert.equal(line.translateToString(trimRight, 0, 4, columns), 'a 𝄞 ');\n        assert.deepEqual(columns, [0, 1, 2, 2, 3, 4]);\n        assert.equal(line.translateToString(trimRight, 0, 3, columns), 'a 𝄞');\n        assert.deepEqual(columns, [0, 1, 2, 2, 3]);\n      }\n    });\n    it('combining', function(): void {\n      const columns: number[] = [];\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, 'e\\u0301', 1));\n      line.setCell(4, createCellData(1, 'e\\u0301', 1));\n      line.setCell(5, createCellData(1, 'e\\u0301', 1));\n      assert.equal(line.translateToString(false, undefined, undefined, columns), 'a e\\u0301 e\\u0301e\\u0301    ');\n      assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), 'a e\\u0301 e\\u0301e\\u0301');\n      assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5, 5, 6]);\n      for (const trimRight of [true, false]) {\n        assert.equal(line.translateToString(trimRight, 0, 5, columns), 'a e\\u0301 e\\u0301');\n        assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5]);\n        assert.equal(line.translateToString(trimRight, 0, 4, columns), 'a e\\u0301 ');\n        assert.deepEqual(columns, [0, 1, 2, 2, 3, 4]);\n        assert.equal(line.translateToString(trimRight, 0, 3, columns), 'a e\\u0301');\n        assert.deepEqual(columns, [0, 1, 2, 2, 3]);\n      }\n    });\n    it('fullwidth', function(): void {\n      const columns: number[] = [];\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, '１', 2));\n      line.setCell(3, createCellData(0, '', 0));\n      line.setCell(5, createCellData(1, '１', 2));\n      line.setCell(6, createCellData(0, '', 0));\n      line.setCell(7, createCellData(1, '１', 2));\n      line.setCell(8, createCellData(0, '', 0));\n      assert.equal(line.translateToString(false, undefined, undefined, columns), 'a １ １１ ');\n      assert.deepEqual(columns, [0, 1, 2, 4, 5, 7, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), 'a １ １１');\n      assert.deepEqual(columns, [0, 1, 2, 4, 5, 7, 9]);\n      for (const trimRight of [true, false]) {\n        assert.equal(line.translateToString(trimRight, 0, 7, columns), 'a １ １');\n        assert.deepEqual(columns, [0, 1, 2, 4, 5, 7]);\n        assert.equal(line.translateToString(trimRight, 0, 6, columns), 'a １ １');\n        assert.deepEqual(columns, [0, 1, 2, 4, 5, 7]);\n        assert.equal(line.translateToString(trimRight, 0, 5, columns), 'a １ ');\n        assert.deepEqual(columns, [0, 1, 2, 4, 5]);\n        assert.equal(line.translateToString(trimRight, 0, 4, columns), 'a １');\n        assert.deepEqual(columns, [0, 1, 2, 4]);\n        assert.equal(line.translateToString(trimRight, 0, 3, columns), 'a １');\n        assert.deepEqual(columns, [0, 1, 2, 4]);\n        assert.equal(line.translateToString(trimRight, 0, 2, columns), 'a ');\n        assert.deepEqual(columns, [0, 1, 2]);\n      }\n    });\n    it('space at end', function(): void {\n      const columns: number[] = [];\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      line.setCell(2, createCellData(1, 'a', 1));\n      line.setCell(4, createCellData(1, 'a', 1));\n      line.setCell(5, createCellData(1, 'a', 1));\n      line.setCell(6, createCellData(1, ' ', 1));\n      assert.equal(line.translateToString(false, undefined, undefined, columns), 'a a aa    ');\n      assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), 'a a aa ');\n      assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7]);\n    });\n    it('should always return some sane value', function(): void {\n      const columns: number[] = [];\n      // sanity check - broken line with invalid out of bound null width cells\n      // this can atm happen with deleting/inserting chars in inputhandler by \"breaking\"\n      // fullwidth pairs --> needs to be fixed after settling BufferLine impl\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      assert.equal(line.translateToString(false, undefined, undefined, columns), '          ');\n      assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n      assert.equal(line.translateToString(true, undefined, undefined, columns), '');\n      assert.deepEqual(columns, [0]);\n    });\n    it('should work with endCol=0', () => {\n      const columns: number[] = [];\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      line.setCell(0, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(true, 0, 0, columns), '');\n      assert.deepEqual(columns, [0]);\n    });\n  });\n  describe('addCharToCell', () => {\n    it('should set width to 1 for empty cell', () => {\n      const line = new TestBufferLine(3, NULL_CELL_DATA, false);\n      line.addCodepointToCell(0, '\\u0301'.charCodeAt(0), 0);\n      const cell = line.loadCell(0, new CellData());\n      // chars contains single combining char\n      // width is set to 1\n      assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\\u0301', 1, 0x0301]);\n      // do not account a single combining char as combined\n      assert.equal(cell.isCombined(), 0);\n    });\n    it('should add char to combining string in cell', () => {\n      const line = new TestBufferLine(3, NULL_CELL_DATA, false);\n      const cell = line .loadCell(0, new CellData());\n      cell.setFromCharData([123, 'e\\u0301', 1, 'e\\u0301'.charCodeAt(1)]);\n      line.setCell(0, cell);\n      line.addCodepointToCell(0, '\\u0301'.charCodeAt(0), 0);\n      line.loadCell(0, cell);\n      // chars contains 3 chars\n      // width is set to 1\n      assert.deepEqual(cell.getAsCharData(), [123, 'e\\u0301\\u0301', 1, 0x0301]);\n      // do not account a single combining char as combined\n      assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);\n    });\n    it('should create combining string on taken cell', () => {\n      const line = new TestBufferLine(3, NULL_CELL_DATA, false);\n      const cell = line .loadCell(0, new CellData());\n      cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]);\n      line.setCell(0, cell);\n      line.addCodepointToCell(0, '\\u0301'.charCodeAt(0), 0);\n      line.loadCell(0, cell);\n      // chars contains 2 chars\n      // width is set to 1\n      assert.deepEqual(cell.getAsCharData(), [123, 'e\\u0301', 1, 0x0301]);\n      // do not account a single combining char as combined\n      assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);\n    });\n  });\n  describe('correct fullwidth handling', () => {\n    function populate(line: BufferLine): void {\n      const cell = createCellData(1, '￥', 2);\n      for (let i = 0; i < line.length; i += 2) {\n        line.setCell(i, cell);\n      }\n    }\n    it('insert - wide char at pos', () => {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.insertCells(9, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), '￥￥￥￥ a');\n      line.insertCells(8, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), '￥￥￥￥a ');\n      line.insertCells(1, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' a ￥￥￥a');\n    });\n    it('insert - wide char at end', () => {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.insertCells(0, 3, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaa￥￥￥ ');\n      line.insertCells(4, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaa a ￥￥');\n      line.insertCells(4, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaa aa ￥ ');\n    });\n    it('delete', () => {\n      const line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.deleteCells(0, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' ￥￥￥￥a');\n      line.deleteCells(5, 2, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' ￥￥￥aaa');\n      line.deleteCells(0, 2, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' ￥￥aaaaa');\n    });\n    it('replace - start at 0', () => {\n      let line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(0, 1, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'a ￥￥￥￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(0, 2, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aa￥￥￥￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(0, 3, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaa ￥￥￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(0, 8, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaaaaaaa￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(0, 9, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaaaaaaaa ');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(0, 10, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), 'aaaaaaaaaa');\n    });\n    it('replace - start at 1', () => {\n      let line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(1, 2, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' a￥￥￥￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(1, 3, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' aa ￥￥￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(1, 4, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' aaa￥￥￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(1, 8, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' aaaaaaa￥');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(1, 9, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' aaaaaaaa ');\n      line = new TestBufferLine(10, NULL_CELL_DATA, false);\n      populate(line);\n      line.replaceCells(1, 10, createCellData(1, 'a', 1));\n      assert.equal(line.translateToString(), ' aaaaaaaaa');\n    });\n  });\n  describe('extended attributes', () => {\n    it('setCells', function(): void {\n      const line = new TestBufferLine(5);\n      const cell = createCellData(1, 'a', 1);\n      // no eAttrs\n      line.setCell(0, cell);\n\n      // some underline style\n      cell.extended.underlineStyle = UnderlineStyle.CURLY;\n      cell.bg |= BgFlags.HAS_EXTENDED;\n      line.setCell(1, cell);\n\n      // same eAttr, different codepoint\n      cell.content = createCellData(1, 'A', 1).content;\n      line.setCell(2, cell);\n\n      // different eAttr\n      cell.extended = cell.extended.clone();\n      cell.extended.underlineStyle = UnderlineStyle.DOTTED;\n      line.setCell(3, cell);\n\n      // no eAttrs again\n      cell.bg &= ~BgFlags.HAS_EXTENDED;\n      line.setCell(4, cell);\n\n      assert.deepEqual(line.toArray(), [\n        [1, 'a', 1, 'a'.charCodeAt(0)],\n        [1, 'a', 1, 'a'.charCodeAt(0)],\n        [1, 'A', 1, 'A'.charCodeAt(0)],\n        [1, 'A', 1, 'A'.charCodeAt(0)],\n        [1, 'A', 1, 'A'.charCodeAt(0)]\n      ]);\n      assert.equal(extendedAttributes(line, 0), undefined);\n      assert.equal(extendedAttributes(line, 1)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 2)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 3)?.underlineStyle, UnderlineStyle.DOTTED);\n      assert.equal(extendedAttributes(line, 4)?.underlineStyle, undefined);\n      // should be ref to the same object\n      assert.equal(extendedAttributes(line, 1), extendedAttributes(line, 2));\n      // should be a different obj\n      assert.notEqual(extendedAttributes(line, 1), extendedAttributes(line, 3));\n    });\n    it('loadCell', () => {\n      const line = new TestBufferLine(5);\n      const cell = createCellData(1, 'a', 1);\n      // no eAttrs\n      line.setCell(0, cell);\n\n      // some underline style\n      cell.extended.underlineStyle = UnderlineStyle.CURLY;\n      cell.bg |= BgFlags.HAS_EXTENDED;\n      line.setCell(1, cell);\n\n      // same eAttr, different codepoint\n      cell.content = 65;  // 'A'\n      line.setCell(2, cell);\n\n      // different eAttr\n      cell.extended = cell.extended.clone();\n      cell.extended.underlineStyle = UnderlineStyle.DOTTED;\n      line.setCell(3, cell);\n\n      // no eAttrs again\n      cell.bg &= ~BgFlags.HAS_EXTENDED;\n      line.setCell(4, cell);\n\n      const cell0 = new CellData();\n      line.loadCell(0, cell0);\n      const cell1 = new CellData();\n      line.loadCell(1, cell1);\n      const cell2 = new CellData();\n      line.loadCell(2, cell2);\n      const cell3 = new CellData();\n      line.loadCell(3, cell3);\n      const cell4 = new CellData();\n      line.loadCell(4, cell4);\n\n      assert.equal(cell0.extended.underlineStyle, UnderlineStyle.NONE);\n      assert.equal(cell1.extended.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(cell2.extended.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(cell3.extended.underlineStyle, UnderlineStyle.DOTTED);\n      assert.equal(cell4.extended.underlineStyle, UnderlineStyle.NONE);\n      assert.equal(cell1.extended, cell2.extended);\n      assert.notEqual(cell2.extended, cell3.extended);\n    });\n    it('fill', () => {\n      const line = new TestBufferLine(3);\n      const cell = createCellData(1, 'a', 1);\n      cell.extended.underlineStyle = UnderlineStyle.CURLY;\n      cell.bg |= BgFlags.HAS_EXTENDED;\n      line.fill(cell);\n      assert.equal(extendedAttributes(line, 0)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 1)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 2)?.underlineStyle, UnderlineStyle.CURLY);\n    });\n    it('insertCells', () => {\n      const line = new TestBufferLine(5);\n      const cell = createCellData(1, 'a', 1);\n      cell.extended.underlineStyle = UnderlineStyle.CURLY;\n      cell.bg |= BgFlags.HAS_EXTENDED;\n      line.insertCells(1, 3, cell);\n      assert.equal(extendedAttributes(line, 1)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 2)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 3)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 4), undefined);\n      cell.extended = cell.extended.clone();\n      cell.extended.underlineStyle = UnderlineStyle.DOTTED;\n      line.insertCells(2, 2, cell);\n      assert.equal(extendedAttributes(line, 1)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 2)?.underlineStyle, UnderlineStyle.DOTTED);\n      assert.equal(extendedAttributes(line, 3)?.underlineStyle, UnderlineStyle.DOTTED);\n      assert.equal(extendedAttributes(line, 4)?.underlineStyle, UnderlineStyle.CURLY);\n    });\n    it('deleteCells', () => {\n      const line = new TestBufferLine(5);\n      const fillCell = createCellData(1, 'a', 1);\n      fillCell.extended.underlineStyle = UnderlineStyle.CURLY;\n      fillCell.bg |= BgFlags.HAS_EXTENDED;\n      line.fill(fillCell);\n      fillCell.extended = fillCell.extended.clone();\n      fillCell.extended.underlineStyle = UnderlineStyle.DOUBLE;\n      line.deleteCells(1, 3, fillCell);\n      assert.equal(extendedAttributes(line, 0)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 1)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 2)?.underlineStyle, UnderlineStyle.DOUBLE);\n      assert.equal(extendedAttributes(line, 3)?.underlineStyle, UnderlineStyle.DOUBLE);\n      assert.equal(extendedAttributes(line, 4)?.underlineStyle, UnderlineStyle.DOUBLE);\n    });\n    it('replaceCells', () => {\n      const line = new TestBufferLine(5);\n      const fillCell = createCellData(1, 'a', 1);\n      fillCell.extended.underlineStyle = UnderlineStyle.CURLY;\n      fillCell.bg |= BgFlags.HAS_EXTENDED;\n      line.fill(fillCell);\n      fillCell.extended = fillCell.extended.clone();\n      fillCell.extended.underlineStyle = UnderlineStyle.DOUBLE;\n      line.replaceCells(1, 3, fillCell);\n      assert.equal(extendedAttributes(line, 0)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 1)?.underlineStyle, UnderlineStyle.DOUBLE);\n      assert.equal(extendedAttributes(line, 2)?.underlineStyle, UnderlineStyle.DOUBLE);\n      assert.equal(extendedAttributes(line, 3)?.underlineStyle, UnderlineStyle.CURLY);\n      assert.equal(extendedAttributes(line, 4)?.underlineStyle, UnderlineStyle.CURLY);\n    });\n    it('clone', () => {\n      const line = new TestBufferLine(5);\n      const cell = createCellData(1, 'a', 1);\n      // no eAttrs\n      line.setCell(0, cell);\n\n      // some underline style\n      cell.extended.underlineStyle = UnderlineStyle.CURLY;\n      cell.bg |= BgFlags.HAS_EXTENDED;\n      line.setCell(1, cell);\n\n      // same eAttr, different codepoint\n      cell.content = 65;  // 'A'\n      line.setCell(2, cell);\n\n      // different eAttr\n      cell.extended = cell.extended.clone();\n      cell.extended.underlineStyle = UnderlineStyle.DOTTED;\n      line.setCell(3, cell);\n\n      // no eAttrs again\n      cell.bg &= ~BgFlags.HAS_EXTENDED;\n      line.setCell(4, cell);\n\n      const nLine = line.clone();\n      assert.equal(extendedAttributes(nLine, 0), extendedAttributes(line, 0));\n      assert.equal(extendedAttributes(nLine, 1), extendedAttributes(line, 1));\n      assert.equal(extendedAttributes(nLine, 2), extendedAttributes(line, 2));\n      assert.equal(extendedAttributes(nLine, 3), extendedAttributes(line, 3));\n      assert.equal(extendedAttributes(nLine, 4), extendedAttributes(line, 4));\n    });\n    it('copyFrom', () => {\n      const initial = new TestBufferLine(5);\n      const cell = createCellData(1, 'a', 1);\n      // no eAttrs\n      initial.setCell(0, cell);\n\n      // some underline style\n      cell.extended.underlineStyle = UnderlineStyle.CURLY;\n      cell.bg |= BgFlags.HAS_EXTENDED;\n      initial.setCell(1, cell);\n\n      // same eAttr, different codepoint\n      cell.content = 65;  // 'A'\n      initial.setCell(2, cell);\n\n      // different eAttr\n      cell.extended = cell.extended.clone();\n      cell.extended.underlineStyle = UnderlineStyle.DOTTED;\n      initial.setCell(3, cell);\n\n      // no eAttrs again\n      cell.bg &= ~BgFlags.HAS_EXTENDED;\n      initial.setCell(4, cell);\n\n      const line = new TestBufferLine(5);\n      line.fill(createCellData(1, 'b', 1));\n      line.copyFrom(initial);\n      assert.equal(extendedAttributes(line, 0), extendedAttributes(initial, 0));\n      assert.equal(extendedAttributes(line, 1), extendedAttributes(initial, 1));\n      assert.equal(extendedAttributes(line, 2), extendedAttributes(initial, 2));\n      assert.equal(extendedAttributes(line, 3), extendedAttributes(initial, 3));\n      assert.equal(extendedAttributes(line, 4), extendedAttributes(initial, 4));\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/buffer/BufferLine.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CharData, IAttributeData, IBufferLine, ICellData, IExtendedAttrs } from 'common/Types';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { CellData } from 'common/buffer/CellData';\nimport { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';\nimport { stringFromCodePoint } from 'common/input/TextDecoder';\n\n/**\n * buffer memory layout:\n *\n *   |             uint32_t             |        uint32_t         |        uint32_t         |\n *   |             `content`            |          `FG`           |          `BG`           |\n *   | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |\n */\n\n\n/** typed array slots taken by one cell */\nconst CELL_SIZE = 3;\n\n/**\n * Cell member indices.\n *\n * Direct access:\n *    `content = data[column * CELL_SIZE + Cell.CONTENT];`\n *    `fg = data[column * CELL_SIZE + Cell.FG];`\n *    `bg = data[column * CELL_SIZE + Cell.BG];`\n */\nconst enum Cell {\n  CONTENT = 0,\n  FG = 1, // currently simply holds all known attrs\n  BG = 2  // currently unused\n}\n\nexport const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());\n\n// Work variables to avoid garbage collection\nlet $startIndex = 0;\nconst $workCell = new CellData();\n\n/** Factor when to cleanup underlying array buffer after shrinking. */\nconst CLEANUP_THRESHOLD = 2;\n\n/**\n * Typed array based bufferline implementation.\n *\n * There are 2 ways to insert data into the cell buffer:\n * - `setCellFromCodepoint` + `addCodepointToCell`\n *   Use these for data that is already UTF32.\n *   Used during normal input in `InputHandler` for faster buffer access.\n * - `setCell`\n *   This method takes a CellData object and stores the data in the buffer.\n *   Use `CellData.fromCharData` to create the CellData object (e.g. from JS string).\n *\n * To retrieve data from the buffer use either one of the primitive methods\n * (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop\n * memory allocs / GC pressure can be greatly reduced by reusing the CellData object.\n */\nexport class BufferLine implements IBufferLine {\n  protected _data: Uint32Array;\n  protected _combined: {[index: number]: string} = {};\n  protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};\n  public length: number;\n\n  constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {\n    this._data = new Uint32Array(cols * CELL_SIZE);\n    const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n    for (let i = 0; i < cols; ++i) {\n      this.setCell(i, cell);\n    }\n    this.length = cols;\n  }\n\n  /**\n   * Get cell data CharData.\n   * @deprecated\n   */\n  public get(index: number): CharData {\n    const content = this._data[index * CELL_SIZE + Cell.CONTENT];\n    const cp = content & Content.CODEPOINT_MASK;\n    return [\n      this._data[index * CELL_SIZE + Cell.FG],\n      (content & Content.IS_COMBINED_MASK)\n        ? this._combined[index]\n        : (cp) ? stringFromCodePoint(cp) : '',\n      content >> Content.WIDTH_SHIFT,\n      (content & Content.IS_COMBINED_MASK)\n        ? this._combined[index].charCodeAt(this._combined[index].length - 1)\n        : cp\n    ];\n  }\n\n  /**\n   * Set cell data from CharData.\n   * @deprecated\n   */\n  public set(index: number, value: CharData): void {\n    this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];\n    if (value[CHAR_DATA_CHAR_INDEX].length > 1) {\n      this._combined[index] = value[1];\n      this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n    } else {\n      this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n    }\n  }\n\n  /**\n   * primitive getters\n   * use these when only one value is needed, otherwise use `loadCell`\n   */\n  public getWidth(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;\n  }\n\n  /** Test whether content has width. */\n  public hasWidth(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;\n  }\n\n  /** Get FG cell component. */\n  public getFg(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.FG];\n  }\n\n  /** Get BG cell component. */\n  public getBg(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.BG];\n  }\n\n  /**\n   * Test whether contains any chars.\n   * Basically an empty has no content, but other cells might differ in FG/BG\n   * from real empty cells.\n   */\n  public hasContent(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;\n  }\n\n  /**\n   * Get codepoint of the cell.\n   * To be in line with `code` in CharData this either returns\n   * a single UTF32 codepoint or the last codepoint of a combined string.\n   */\n  public getCodePoint(index: number): number {\n    const content = this._data[index * CELL_SIZE + Cell.CONTENT];\n    if (content & Content.IS_COMBINED_MASK) {\n      return this._combined[index].charCodeAt(this._combined[index].length - 1);\n    }\n    return content & Content.CODEPOINT_MASK;\n  }\n\n  /** Test whether the cell contains a combined string. */\n  public isCombined(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;\n  }\n\n  /** Returns the string content of the cell. */\n  public getString(index: number): string {\n    const content = this._data[index * CELL_SIZE + Cell.CONTENT];\n    if (content & Content.IS_COMBINED_MASK) {\n      return this._combined[index];\n    }\n    if (content & Content.CODEPOINT_MASK) {\n      return stringFromCodePoint(content & Content.CODEPOINT_MASK);\n    }\n    // return empty string for empty cells\n    return '';\n  }\n\n  /** Get state of protected flag. */\n  public isProtected(index: number): number {\n    return this._data[index * CELL_SIZE + Cell.BG] & BgFlags.PROTECTED;\n  }\n\n  /**\n   * Load data at `index` into `cell`. This is used to access cells in a way that's more friendly\n   * to GC as it significantly reduced the amount of new objects/references needed.\n   */\n  public loadCell(index: number, cell: ICellData): ICellData {\n    $startIndex = index * CELL_SIZE;\n    cell.content = this._data[$startIndex + Cell.CONTENT];\n    cell.fg = this._data[$startIndex + Cell.FG];\n    cell.bg = this._data[$startIndex + Cell.BG];\n    if (cell.content & Content.IS_COMBINED_MASK) {\n      cell.combinedData = this._combined[index];\n    }\n    if (cell.bg & BgFlags.HAS_EXTENDED) {\n      cell.extended = this._extendedAttrs[index]!;\n    }\n    return cell;\n  }\n\n  /**\n   * Set data at `index` to `cell`.\n   */\n  public setCell(index: number, cell: ICellData): void {\n    if (cell.content & Content.IS_COMBINED_MASK) {\n      this._combined[index] = cell.combinedData;\n    }\n    if (cell.bg & BgFlags.HAS_EXTENDED) {\n      this._extendedAttrs[index] = cell.extended;\n    }\n    this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;\n    this._data[index * CELL_SIZE + Cell.FG] = cell.fg;\n    this._data[index * CELL_SIZE + Cell.BG] = cell.bg;\n  }\n\n  /**\n   * Set cell data from input handler.\n   * Since the input handler see the incoming chars as UTF32 codepoints,\n   * it gets an optimized access method.\n   */\n  public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {\n    if (attrs.bg & BgFlags.HAS_EXTENDED) {\n      this._extendedAttrs[index] = attrs.extended;\n    }\n    this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);\n    this._data[index * CELL_SIZE + Cell.FG] = attrs.fg;\n    this._data[index * CELL_SIZE + Cell.BG] = attrs.bg;\n  }\n\n  /**\n   * Add a codepoint to a cell from input handler.\n   * During input stage combining chars with a width of 0 follow and stack\n   * onto a leading char. Since we already set the attrs\n   * by the previous `setDataFromCodePoint` call, we can omit it here.\n   */\n  public addCodepointToCell(index: number, codePoint: number, width: number): void {\n    let content = this._data[index * CELL_SIZE + Cell.CONTENT];\n    if (content & Content.IS_COMBINED_MASK) {\n      // we already have a combined string, simply add\n      this._combined[index] += stringFromCodePoint(codePoint);\n    } else {\n      if (content & Content.CODEPOINT_MASK) {\n        // normal case for combining chars:\n        //  - move current leading char + new one into combined string\n        //  - set combined flag\n        this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);\n        content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0\n        content |= Content.IS_COMBINED_MASK;\n      } else {\n        // should not happen - we actually have no data in the cell yet\n        // simply set the data in the cell buffer with a width of 1\n        content = codePoint | (1 << Content.WIDTH_SHIFT);\n      }\n    }\n    if (width) {\n      content &= ~Content.WIDTH_MASK;\n      content |= width << Content.WIDTH_SHIFT;\n    }\n    this._data[index * CELL_SIZE + Cell.CONTENT] = content;\n  }\n\n  public insertCells(pos: number, n: number, fillCellData: ICellData): void {\n    pos %= this.length;\n\n    // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char\n    if (pos && this.getWidth(pos - 1) === 2) {\n      this.setCellFromCodepoint(pos - 1, 0, 1, fillCellData);\n    }\n\n    if (n < this.length - pos) {\n      for (let i = this.length - pos - n - 1; i >= 0; --i) {\n        this.setCell(pos + n + i, this.loadCell(pos + i, $workCell));\n      }\n      for (let i = 0; i < n; ++i) {\n        this.setCell(pos + i, fillCellData);\n      }\n    } else {\n      for (let i = pos; i < this.length; ++i) {\n        this.setCell(i, fillCellData);\n      }\n    }\n\n    // handle fullwidth at line end: reset last cell if it is first cell of a wide char\n    if (this.getWidth(this.length - 1) === 2) {\n      this.setCellFromCodepoint(this.length - 1, 0, 1, fillCellData);\n    }\n  }\n\n  public deleteCells(pos: number, n: number, fillCellData: ICellData): void {\n    pos %= this.length;\n    if (n < this.length - pos) {\n      for (let i = 0; i < this.length - pos - n; ++i) {\n        this.setCell(pos + i, this.loadCell(pos + n + i, $workCell));\n      }\n      for (let i = this.length - n; i < this.length; ++i) {\n        this.setCell(i, fillCellData);\n      }\n    } else {\n      for (let i = pos; i < this.length; ++i) {\n        this.setCell(i, fillCellData);\n      }\n    }\n\n    // handle fullwidth at pos:\n    // - reset pos-1 if wide char\n    // - reset pos if width==0 (previous second cell of a wide char)\n    if (pos && this.getWidth(pos - 1) === 2) {\n      this.setCellFromCodepoint(pos - 1, 0, 1, fillCellData);\n    }\n    if (this.getWidth(pos) === 0 && !this.hasContent(pos)) {\n      this.setCellFromCodepoint(pos, 0, 1, fillCellData);\n    }\n  }\n\n  public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {\n    // full branching on respectProtect==true, hopefully getting fast JIT for standard case\n    if (respectProtect) {\n      if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {\n        this.setCellFromCodepoint(start - 1, 0, 1, fillCellData);\n      }\n      if (end < this.length && this.getWidth(end - 1) === 2 && !this.isProtected(end)) {\n        this.setCellFromCodepoint(end, 0, 1, fillCellData);\n      }\n      while (start < end  && start < this.length) {\n        if (!this.isProtected(start)) {\n          this.setCell(start, fillCellData);\n        }\n        start++;\n      }\n      return;\n    }\n\n    // handle fullwidth at start: reset cell one to the left if start is second cell of a wide char\n    if (start && this.getWidth(start - 1) === 2) {\n      this.setCellFromCodepoint(start - 1, 0, 1, fillCellData);\n    }\n    // handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char\n    if (end < this.length && this.getWidth(end - 1) === 2) {\n      this.setCellFromCodepoint(end, 0, 1, fillCellData);\n    }\n\n    while (start < end  && start < this.length) {\n      this.setCell(start++, fillCellData);\n    }\n  }\n\n  /**\n   * Resize BufferLine to `cols` filling excess cells with `fillCellData`.\n   * The underlying array buffer will not change if there is still enough space\n   * to hold the new buffer line data.\n   * Returns a boolean indicating, whether a `cleanupMemory` call would free\n   * excess memory (true after shrinking > CLEANUP_THRESHOLD).\n   */\n  public resize(cols: number, fillCellData: ICellData): boolean {\n    if (cols === this.length) {\n      return this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;\n    }\n    const uint32Cells = cols * CELL_SIZE;\n    if (cols > this.length) {\n      if (this._data.buffer.byteLength >= uint32Cells * 4) {\n        // optimization: avoid alloc and data copy if buffer has enough room\n        this._data = new Uint32Array(this._data.buffer, 0, uint32Cells);\n      } else {\n        // slow path: new alloc and full data copy\n        const data = new Uint32Array(uint32Cells);\n        data.set(this._data);\n        this._data = data;\n      }\n      for (let i = this.length; i < cols; ++i) {\n        this.setCell(i, fillCellData);\n      }\n    } else {\n      // optimization: just shrink the view on existing buffer\n      this._data = this._data.subarray(0, uint32Cells);\n      // Remove any cut off combined data\n      const keys = Object.keys(this._combined);\n      for (let i = 0; i < keys.length; i++) {\n        const key = parseInt(keys[i], 10);\n        if (key >= cols) {\n          delete this._combined[key];\n        }\n      }\n      // remove any cut off extended attributes\n      const extKeys = Object.keys(this._extendedAttrs);\n      for (let i = 0; i < extKeys.length; i++) {\n        const key = parseInt(extKeys[i], 10);\n        if (key >= cols) {\n          delete this._extendedAttrs[key];\n        }\n      }\n    }\n    this.length = cols;\n    return uint32Cells * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;\n  }\n\n  /**\n   * Cleanup underlying array buffer.\n   * A cleanup will be triggered if the array buffer exceeds the actual used\n   * memory by a factor of CLEANUP_THRESHOLD.\n   * Returns 0 or 1 indicating whether a cleanup happened.\n   */\n  public cleanupMemory(): number {\n    if (this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength) {\n      const data = new Uint32Array(this._data.length);\n      data.set(this._data);\n      this._data = data;\n      return 1;\n    }\n    return 0;\n  }\n\n  /** fill a line with fillCharData */\n  public fill(fillCellData: ICellData, respectProtect: boolean = false): void {\n    // full branching on respectProtect==true, hopefully getting fast JIT for standard case\n    if (respectProtect) {\n      for (let i = 0; i < this.length; ++i) {\n        if (!this.isProtected(i)) {\n          this.setCell(i, fillCellData);\n        }\n      }\n      return;\n    }\n    this._combined = {};\n    this._extendedAttrs = {};\n    for (let i = 0; i < this.length; ++i) {\n      this.setCell(i, fillCellData);\n    }\n  }\n\n  /** alter to a full copy of line  */\n  public copyFrom(line: BufferLine): void {\n    if (this.length !== line.length) {\n      this._data = new Uint32Array(line._data);\n    } else {\n      // use high speed copy if lengths are equal\n      this._data.set(line._data);\n    }\n    this.length = line.length;\n    this._combined = {};\n    for (const el in line._combined) {\n      this._combined[el] = line._combined[el];\n    }\n    this._extendedAttrs = {};\n    for (const el in line._extendedAttrs) {\n      this._extendedAttrs[el] = line._extendedAttrs[el];\n    }\n    this.isWrapped = line.isWrapped;\n  }\n\n  /** create a new clone */\n  public clone(): IBufferLine {\n    const newLine = new BufferLine(0);\n    newLine._data = new Uint32Array(this._data);\n    newLine.length = this.length;\n    for (const el in this._combined) {\n      newLine._combined[el] = this._combined[el];\n    }\n    for (const el in this._extendedAttrs) {\n      newLine._extendedAttrs[el] = this._extendedAttrs[el];\n    }\n    newLine.isWrapped = this.isWrapped;\n    return newLine;\n  }\n\n  public getTrimmedLength(): number {\n    for (let i = this.length - 1; i >= 0; --i) {\n      if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {\n        return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);\n      }\n    }\n    return 0;\n  }\n\n  public getNoBgTrimmedLength(): number {\n    for (let i = this.length - 1; i >= 0; --i) {\n      if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * CELL_SIZE + Cell.BG] & Attributes.CM_MASK)) {\n        return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);\n      }\n    }\n    return 0;\n  }\n\n  public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {\n    const srcData = src._data;\n    if (applyInReverse) {\n      for (let cell = length - 1; cell >= 0; cell--) {\n        for (let i = 0; i < CELL_SIZE; i++) {\n          this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];\n        }\n        if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {\n          this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];\n        }\n      }\n    } else {\n      for (let cell = 0; cell < length; cell++) {\n        for (let i = 0; i < CELL_SIZE; i++) {\n          this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];\n        }\n        if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {\n          this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];\n        }\n      }\n    }\n\n    // Move any combined data over as needed, FIXME: repeat for extended attrs\n    const srcCombinedKeys = Object.keys(src._combined);\n    for (let i = 0; i < srcCombinedKeys.length; i++) {\n      const key = parseInt(srcCombinedKeys[i], 10);\n      if (key >= srcCol) {\n        this._combined[key - srcCol + destCol] = src._combined[key];\n      }\n    }\n  }\n\n  /**\n   * Translates the buffer line to a string.\n   *\n   * @param trimRight Whether to trim any empty cells on the right.\n   * @param startCol The column to start the string (0-based inclusive).\n   * @param endCol The column to end the string (0-based exclusive).\n   * @param outColumns if specified, this array will be filled with column numbers such that\n   * `returnedString[i]` is displayed at `outColumns[i]` column. `outColumns[returnedString.length]`\n   * is where the character following `returnedString` will be displayed.\n   *\n   * When a single cell is translated to multiple UTF-16 code units (e.g. surrogate pair) in the\n   * returned string, the corresponding entries in `outColumns` will have the same column number.\n   */\n  public translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string {\n    startCol = startCol ?? 0;\n    endCol = endCol ?? this.length;\n    if (trimRight) {\n      endCol = Math.min(endCol, this.getTrimmedLength());\n    }\n    if (outColumns) {\n      outColumns.length = 0;\n    }\n    let result = '';\n    while (startCol < endCol) {\n      const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];\n      const cp = content & Content.CODEPOINT_MASK;\n      const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;\n      result += chars;\n      if (outColumns) {\n        for (let i = 0; i < chars.length; ++i) {\n          outColumns.push(startCol);\n        }\n      }\n      startCol += (content >> Content.WIDTH_SHIFT) || 1; // always advance by at least 1\n    }\n    if (outColumns) {\n      outColumns.push(startCol);\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/common/buffer/BufferRange.test.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { getRangeLength } from 'common/buffer/BufferRange';\nimport { IBufferRange } from '@xterm/xterm';\n\ndescribe('BufferRange', () => {\n  describe('getRangeLength', () => {\n    it('should get range for single line', () => {\n      assert.equal(getRangeLength(createRange(1, 1, 4, 1), 0), 4);\n    });\n    it('should throw for invalid range', () => {\n      assert.throws(() => getRangeLength(createRange(1, 3, 1, 1), 0));\n    });\n    it('should get range multiple lines', () => {\n      assert.equal(getRangeLength(createRange(1, 1, 4, 5), 5), 24);\n    });\n    it('should get range for end line right after start line', () => {\n      assert.equal(getRangeLength(createRange(1, 1, 7, 2), 5), 12);\n    });\n  });\n});\n\nfunction createRange(x1: number, y1: number, x2: number, y2: number): IBufferRange {\n  return {\n    start: { x: x1, y: y1 },\n    end: { x: x2, y: y2 }\n  };\n}\n"
  },
  {
    "path": "src/common/buffer/BufferRange.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferRange } from '@xterm/xterm';\n\nexport function getRangeLength(range: IBufferRange, bufferCols: number): number {\n  if (range.start.y > range.end.y) {\n    throw new Error(`Buffer range end (${range.end.x}, ${range.end.y}) cannot be before start (${range.start.x}, ${range.start.y})`);\n  }\n  return bufferCols * (range.end.y - range.start.y) + (range.end.x - range.start.x + 1);\n}\n"
  },
  {
    "path": "src/common/buffer/BufferReflow.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { BufferLine } from 'common/buffer/BufferLine';\nimport { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from 'common/buffer/Constants';\nimport { reflowSmallerGetNewLineLengths } from 'common/buffer/BufferReflow';\n\ndescribe('BufferReflow', () => {\n  describe('reflowSmallerGetNewLineLengths', () => {\n    it('should return correct line lengths for a small line with wide characters', () => {\n      const line = new BufferLine(4);\n      line.set(0, [0, '汉', 2, '汉'.charCodeAt(0)]);\n      line.set(1, [0, '', 0, 0]);\n      line.set(2, [0, '语', 2, '语'.charCodeAt(0)]);\n      line.set(3, [0, '', 0, 0]);\n      assert.equal(line.translateToString(true), '汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 3), [2, 2], 'line: 汉, 语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语');\n    });\n    it('should return correct line lengths for a large line with wide characters', () => {\n      const line = new BufferLine(12);\n      for (let i = 0; i < 12; i += 4) {\n        line.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]);\n        line.set(i + 2, [0, '语', 2, '语'.charCodeAt(0)]);\n      }\n      for (let i = 1; i < 12; i += 2) {\n        line.set(i, [0, '', 0, 0]);\n        line.set(i, [0, '', 0, 0]);\n      }\n      assert.equal(line.translateToString(), '汉语汉语汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 11), [10, 2], 'line: 汉语汉语汉, 语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 10), [10, 2], 'line: 汉语汉语汉, 语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 9), [8, 4], 'line: 汉语汉语, 汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 8), [8, 4], 'line: 汉语汉语, 汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 7), [6, 6], 'line: 汉语汉, 语汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 6), [6, 6], 'line: 汉语汉, 语汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 5), [4, 4, 4], 'line: 汉语, 汉语, 汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 4), [4, 4, 4], 'line: 汉语, 汉语, 汉语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 3), [2, 2, 2, 2, 2, 2], 'line: 汉, 语, 汉, 语, 汉, 语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 2), [2, 2, 2, 2, 2, 2], 'line: 汉, 语, 汉, 语, 汉, 语');\n    });\n    it('should return correct line lengths for a string with wide and single characters', () => {\n      const line = new BufferLine(6);\n      line.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n      line.set(1, [0, '汉', 2, '汉'.charCodeAt(0)]);\n      line.set(2, [0, '', 0, 0]);\n      line.set(3, [0, '语', 2, '语'.charCodeAt(0)]);\n      line.set(4, [0, '', 0, 0]);\n      line.set(5, [0, 'b', 1, 'b'.charCodeAt(0)]);\n      assert.equal(line.translateToString(), 'a汉语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 5), [5, 1], 'line: a汉语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 4), [3, 3], 'line: a汉, 语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 3), [3, 3], 'line: a汉, 语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 2), [1, 2, 2, 1], 'line: a, 汉, 语, b');\n    });\n    it('should return correct line lengths for a wrapped line with wide and single characters', () => {\n      const line1 = new BufferLine(6);\n      line1.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n      line1.set(1, [0, '汉', 2, '汉'.charCodeAt(0)]);\n      line1.set(2, [0, '', 0, 0]);\n      line1.set(3, [0, '语', 2, '语'.charCodeAt(0)]);\n      line1.set(4, [0, '', 0, 0]);\n      line1.set(5, [0, 'b', 1, 'b'.charCodeAt(0)]);\n      const line2 = new BufferLine(6, undefined, true);\n      line2.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]);\n      line2.set(1, [0, '汉', 2, '汉'.charCodeAt(0)]);\n      line2.set(2, [0, '', 0, 0]);\n      line2.set(3, [0, '语', 2, '语'.charCodeAt(0)]);\n      line2.set(4, [0, '', 0, 0]);\n      line2.set(5, [0, 'b', 1, 'b'.charCodeAt(0)]);\n      assert.equal(line1.translateToString(), 'a汉语b');\n      assert.equal(line2.translateToString(), 'a汉语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 5), [5, 4, 3], 'lines: a汉语, ba汉, 语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 4), [3, 4, 4, 1], 'lines: a汉, 语ba, 汉语, b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 3), [3, 3, 3, 3], 'lines: a汉, 语b, a汉, 语b');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 2), [1, 2, 2, 2, 2, 2, 1], 'lines: a, 汉, 语, ba, 汉, 语, b');\n    });\n    it('should work on lines ending in null space', () => {\n      const line = new BufferLine(5);\n      line.set(0, [0, '汉', 2, '汉'.charCodeAt(0)]);\n      line.set(1, [0, '', 0, 0]);\n      line.set(2, [0, '语', 2, '语'.charCodeAt(0)]);\n      line.set(3, [0, '', 0, 0]);\n      line.set(4, [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n      assert.equal(line.translateToString(true), '汉语');\n      assert.equal(line.translateToString(false), '汉语 ');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 3), [2, 2], 'line: 汉, 语');\n      assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语');\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/buffer/BufferReflow.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BufferLine } from 'common/buffer/BufferLine';\nimport { CircularList } from 'common/CircularList';\nimport { IBufferLine, ICellData } from 'common/Types';\n\nexport interface INewLayoutResult {\n  layout: number[];\n  countRemoved: number;\n}\n\n/**\n * Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed\n * when a wrapped line unwraps.\n * @param lines The buffer lines.\n * @param oldCols The columns before resize\n * @param newCols The columns after resize.\n * @param bufferAbsoluteY The absolute y position of the cursor (baseY + cursorY).\n * @param nullCell The cell data to use when filling in empty cells.\n * @param reflowCursorLine Whether to reflow the line containing the cursor.\n */\nexport function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData, reflowCursorLine: boolean): number[] {\n  // Gather all BufferLines that need to be removed from the Buffer here so that they can be\n  // batched up and only committed once\n  const toRemove: number[] = [];\n\n  for (let y = 0; y < lines.length - 1; y++) {\n    // Check if this row is wrapped\n    let i = y;\n    let nextLine = lines.get(++i) as BufferLine;\n    if (!nextLine.isWrapped) {\n      continue;\n    }\n\n    // Check how many lines it's wrapped for\n    const wrappedLines: BufferLine[] = [lines.get(y) as BufferLine];\n    while (i < lines.length && nextLine.isWrapped) {\n      wrappedLines.push(nextLine);\n      nextLine = lines.get(++i) as BufferLine;\n    }\n\n    if (!reflowCursorLine) {\n      // If these lines contain the cursor don't touch them, the program will handle fixing up\n      // wrapped lines with the cursor\n      if (bufferAbsoluteY >= y && bufferAbsoluteY < i) {\n        y += wrappedLines.length - 1;\n        continue;\n      }\n    }\n\n    // Copy buffer data to new locations\n    let destLineIndex = 0;\n    let destCol = getWrappedLineTrimmedLength(wrappedLines, destLineIndex, oldCols);\n    let srcLineIndex = 1;\n    let srcCol = 0;\n    while (srcLineIndex < wrappedLines.length) {\n      const srcTrimmedTineLength = getWrappedLineTrimmedLength(wrappedLines, srcLineIndex, oldCols);\n      const srcRemainingCells = srcTrimmedTineLength - srcCol;\n      const destRemainingCells = newCols - destCol;\n      const cellsToCopy = Math.min(srcRemainingCells, destRemainingCells);\n\n      wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol, destCol, cellsToCopy, false);\n\n      destCol += cellsToCopy;\n      if (destCol === newCols) {\n        destLineIndex++;\n        destCol = 0;\n      }\n      srcCol += cellsToCopy;\n      if (srcCol === srcTrimmedTineLength) {\n        srcLineIndex++;\n        srcCol = 0;\n      }\n\n      // Make sure the last cell isn't wide, if it is copy it to the current dest\n      if (destCol === 0 && destLineIndex !== 0) {\n        if (wrappedLines[destLineIndex - 1].getWidth(newCols - 1) === 2) {\n          wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1, false);\n          // Null out the end of the last row\n          wrappedLines[destLineIndex - 1].setCell(newCols - 1, nullCell);\n        }\n      }\n    }\n\n    // Clear out remaining cells or fragments could remain;\n    wrappedLines[destLineIndex].replaceCells(destCol, newCols, nullCell);\n\n    // Work backwards and remove any rows at the end that only contain null cells\n    let countToRemove = 0;\n    for (let i = wrappedLines.length - 1; i > 0; i--) {\n      if (i > destLineIndex || wrappedLines[i].getTrimmedLength() === 0) {\n        countToRemove++;\n      } else {\n        break;\n      }\n    }\n\n    if (countToRemove > 0) {\n      toRemove.push(y + wrappedLines.length - countToRemove); // index\n      toRemove.push(countToRemove);\n    }\n\n    y += wrappedLines.length - 1;\n  }\n  return toRemove;\n}\n\n/**\n * Creates and return the new layout for lines given an array of indexes to be removed.\n * @param lines The buffer lines.\n * @param toRemove The indexes to remove.\n */\nexport function reflowLargerCreateNewLayout(lines: CircularList<IBufferLine>, toRemove: number[]): INewLayoutResult {\n  const layout: number[] = [];\n  // First iterate through the list and get the actual indexes to use for rows\n  let nextToRemoveIndex = 0;\n  let nextToRemoveStart = toRemove[nextToRemoveIndex];\n  let countRemovedSoFar = 0;\n  for (let i = 0; i < lines.length; i++) {\n    if (nextToRemoveStart === i) {\n      const countToRemove = toRemove[++nextToRemoveIndex];\n\n      // Tell markers that there was a deletion\n      lines.onDeleteEmitter.fire({\n        index: i - countRemovedSoFar,\n        amount: countToRemove\n      });\n\n      i += countToRemove - 1;\n      countRemovedSoFar += countToRemove;\n      nextToRemoveStart = toRemove[++nextToRemoveIndex];\n    } else {\n      layout.push(i);\n    }\n  }\n  return {\n    layout,\n    countRemoved: countRemovedSoFar\n  };\n}\n\n/**\n * Applies a new layout to the buffer. This essentially does the same as many splice calls but it's\n * done all at once in a single iteration through the list since splice is very expensive.\n * @param lines The buffer lines.\n * @param newLayout The new layout to apply.\n */\nexport function reflowLargerApplyNewLayout(lines: CircularList<IBufferLine>, newLayout: number[]): void {\n  // Record original lines so they don't get overridden when we rearrange the list\n  const newLayoutLines: BufferLine[] = [];\n  for (let i = 0; i < newLayout.length; i++) {\n    newLayoutLines.push(lines.get(newLayout[i]) as BufferLine);\n  }\n\n  // Rearrange the list\n  for (let i = 0; i < newLayoutLines.length; i++) {\n    lines.set(i, newLayoutLines[i]);\n  }\n  lines.length = newLayout.length;\n}\n\n/**\n * Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-\n * compute the wrapping points since wide characters may need to be wrapped onto the following line.\n * This function will return an array of numbers of where each line wraps to, the resulting array\n * will only contain the values `newCols` (when the line does not end with a wide character) and\n * `newCols - 1` (when the line does end with a wide character), except for the last value which\n * will contain the remaining items to fill the line.\n *\n * Calling this with a `newCols` value of `1` will lock up.\n *\n * @param wrappedLines The wrapped lines to evaluate.\n * @param oldCols The columns before resize.\n * @param newCols The columns after resize.\n */\nexport function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {\n  const newLineLengths: number[] = [];\n  const cellsNeeded = wrappedLines.map((l, i) => getWrappedLineTrimmedLength(wrappedLines, i, oldCols)).reduce((p, c) => p + c);\n\n  // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and\n  // linesNeeded\n  let srcCol = 0;\n  let srcLine = 0;\n  let cellsAvailable = 0;\n  while (cellsAvailable < cellsNeeded) {\n    if (cellsNeeded - cellsAvailable < newCols) {\n      // Add the final line and exit the loop\n      newLineLengths.push(cellsNeeded - cellsAvailable);\n      break;\n    }\n    srcCol += newCols;\n    const oldTrimmedLength = getWrappedLineTrimmedLength(wrappedLines, srcLine, oldCols);\n    if (srcCol > oldTrimmedLength) {\n      srcCol -= oldTrimmedLength;\n      srcLine++;\n    }\n    const endsWithWide = wrappedLines[srcLine].getWidth(srcCol - 1) === 2;\n    if (endsWithWide) {\n      srcCol--;\n    }\n    const lineLength = endsWithWide ? newCols - 1 : newCols;\n    newLineLengths.push(lineLength);\n    cellsAvailable += lineLength;\n  }\n\n  return newLineLengths;\n}\n\nexport function getWrappedLineTrimmedLength(lines: BufferLine[], i: number, cols: number): number {\n  // If this is the last row in the wrapped line, get the actual trimmed length\n  if (i === lines.length - 1) {\n    return lines[i].getTrimmedLength();\n  }\n  // Detect whether the following line starts with a wide character and the end of the current line\n  // is null, if so then we can be pretty sure the null character should be excluded from the line\n  // length]\n  const endsInNull = !(lines[i].hasContent(cols - 1)) && lines[i].getWidth(cols - 1) === 1;\n  const followingLineStartsWithWide = lines[i + 1].getWidth(0) === 2;\n  if (endsInNull && followingLineStartsWithWide) {\n    return cols - 1;\n  }\n  return cols;\n}\n"
  },
  {
    "path": "src/common/buffer/BufferSet.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { BufferSet } from 'common/buffer/BufferSet';\nimport { Buffer } from 'common/buffer/Buffer';\nimport { MockOptionsService, MockBufferService, MockLogService } from 'common/TestUtils.test';\n\ndescribe('BufferSet', () => {\n  let bufferSet: BufferSet;\n\n  beforeEach(() => {\n    bufferSet = new BufferSet(\n      new MockOptionsService({ scrollback: 1000 }),\n      new MockBufferService(80, 24),\n      new MockLogService()\n    );\n  });\n\n  describe('constructor', () => {\n    it('should create two different buffers: alt and normal', () => {\n      assert.instanceOf(bufferSet.normal, Buffer);\n      assert.instanceOf(bufferSet.alt, Buffer);\n      assert.notEqual(bufferSet.normal, bufferSet.alt);\n    });\n  });\n\n  describe('activateNormalBuffer', () => {\n    beforeEach(() => {\n      bufferSet.activateNormalBuffer();\n    });\n\n    it('should set the normal buffer as the currently active buffer', () => {\n      assert.equal(bufferSet.active, bufferSet.normal);\n    });\n  });\n\n  describe('activateAltBuffer', () => {\n    beforeEach(() => {\n      bufferSet.activateAltBuffer();\n    });\n\n    it('should set the alt buffer as the currently active buffer', () => {\n      assert.equal(bufferSet.active, bufferSet.alt);\n    });\n  });\n\n  describe('cursor handling when swapping buffers', () => {\n    beforeEach(() => {\n      bufferSet.normal.x = 0;\n      bufferSet.normal.y = 0;\n      bufferSet.alt.x = 0;\n      bufferSet.alt.y = 0;\n    });\n\n    it('should keep the cursor stationary when activating alt buffer', () => {\n      bufferSet.activateNormalBuffer();\n      bufferSet.active.x = 30;\n      bufferSet.active.y = 10;\n      bufferSet.activateAltBuffer();\n      assert.equal(bufferSet.active.x, 30);\n      assert.equal(bufferSet.active.y, 10);\n    });\n    it('should keep the cursor stationary when activating normal buffer', () => {\n      bufferSet.activateAltBuffer();\n      bufferSet.active.x = 30;\n      bufferSet.active.y = 10;\n      bufferSet.activateNormalBuffer();\n      assert.equal(bufferSet.active.x, 30);\n      assert.equal(bufferSet.active.y, 10);\n    });\n  });\n\n  describe('markers', () => {\n    it('should clear the markers when the buffer is switched', () => {\n      bufferSet.activateAltBuffer();\n      bufferSet.alt.addMarker(1);\n      assert.equal(bufferSet.alt.markers.length, 1);\n      bufferSet.activateNormalBuffer();\n      assert.equal(bufferSet.alt.markers.length, 0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/buffer/BufferSet.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Disposable } from 'common/Lifecycle';\nimport { IAttributeData } from 'common/Types';\nimport { Buffer } from 'common/buffer/Buffer';\nimport { IBuffer, IBufferSet } from 'common/buffer/Types';\nimport { IBufferService, ILogService, IOptionsService } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet extends Disposable implements IBufferSet {\n  private _normal!: Buffer;\n  private _alt!: Buffer;\n  private _activeBuffer!: Buffer;\n\n  private readonly _onBufferActivate = this._register(new Emitter<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>());\n  public readonly onBufferActivate = this._onBufferActivate.event;\n\n  /**\n   * Create a new BufferSet for the given terminal.\n   */\n  constructor(\n    private readonly _optionsService: IOptionsService,\n    private readonly _bufferService: IBufferService,\n    private readonly _logService: ILogService\n  ) {\n    super();\n    this.reset();\n    this._register(this._optionsService.onSpecificOptionChange('scrollback', () => this.resize(this._bufferService.cols, this._bufferService.rows)));\n    this._register(this._optionsService.onSpecificOptionChange('tabStopWidth', () => this.setupTabStops()));\n  }\n\n  public reset(): void {\n    this._normal = new Buffer(true, this._optionsService, this._bufferService, this._logService);\n    this._normal.fillViewportRows();\n\n    // The alt buffer should never have scrollback.\n    // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer\n    this._alt = new Buffer(false, this._optionsService, this._bufferService, this._logService);\n    this._activeBuffer = this._normal;\n    this._onBufferActivate.fire({\n      activeBuffer: this._normal,\n      inactiveBuffer: this._alt\n    });\n\n    this.setupTabStops();\n  }\n\n  /**\n   * Returns the alt Buffer of the BufferSet\n   */\n  public get alt(): Buffer {\n    return this._alt;\n  }\n\n  /**\n   * Returns the currently active Buffer of the BufferSet\n   */\n  public get active(): Buffer {\n    return this._activeBuffer;\n  }\n\n  /**\n   * Returns the normal Buffer of the BufferSet\n   */\n  public get normal(): Buffer {\n    return this._normal;\n  }\n\n  /**\n   * Sets the normal Buffer of the BufferSet as its currently active Buffer\n   */\n  public activateNormalBuffer(): void {\n    if (this._activeBuffer === this._normal) {\n      return;\n    }\n    this._normal.x = this._alt.x;\n    this._normal.y = this._alt.y;\n    // The alt buffer should always be cleared when we switch to the normal\n    // buffer. This frees up memory since the alt buffer should always be new\n    // when activated.\n    this._alt.clearAllMarkers();\n    this._alt.clear();\n    this._activeBuffer = this._normal;\n    this._onBufferActivate.fire({\n      activeBuffer: this._normal,\n      inactiveBuffer: this._alt\n    });\n  }\n\n  /**\n   * Sets the alt Buffer of the BufferSet as its currently active Buffer\n   */\n  public activateAltBuffer(fillAttr?: IAttributeData): void {\n    if (this._activeBuffer === this._alt) {\n      return;\n    }\n    // Since the alt buffer is always cleared when the normal buffer is\n    // activated, we want to fill it when switching to it.\n    this._alt.fillViewportRows(fillAttr);\n    this._alt.x = this._normal.x;\n    this._alt.y = this._normal.y;\n    this._activeBuffer = this._alt;\n    this._onBufferActivate.fire({\n      activeBuffer: this._alt,\n      inactiveBuffer: this._normal\n    });\n  }\n\n  /**\n   * Resizes both normal and alt buffers, adjusting their data accordingly.\n   * @param newCols The new number of columns.\n   * @param newRows The new number of rows.\n   */\n  public resize(newCols: number, newRows: number): void {\n    this._normal.resize(newCols, newRows);\n    this._alt.resize(newCols, newRows);\n    this.setupTabStops(newCols);\n  }\n\n  /**\n   * Setup the tab stops.\n   * @param i The index to start setting up tab stops from.\n   */\n  public setupTabStops(i?: number): void {\n    this._normal.setupTabStops(i);\n    this._alt.setupTabStops(i);\n  }\n}\n"
  },
  {
    "path": "src/common/buffer/CellData.test.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { Attributes, BgFlags, FgFlags, UnderlineStyle } from 'common/buffer/Constants';\nimport { CellData } from 'common/buffer/CellData';\nimport { assert } from 'chai';\n\nfunction createStyledCell(char: string, underlineStyle: UnderlineStyle, underlineColor: number): CellData {\n  const cell = new CellData();\n  const fg = Attributes.CM_P256 | 12 | FgFlags.BOLD | FgFlags.UNDERLINE;\n  cell.setFromCharData([fg, char, 1, char.charCodeAt(0)]);\n  cell.bg = Attributes.CM_P16 | 2 | BgFlags.ITALIC;\n  cell.extended.underlineStyle = underlineStyle;\n  cell.extended.underlineColor = Attributes.CM_P256 | underlineColor;\n  cell.updateExtended();\n  return cell;\n}\n\ndescribe('CellData', () => {\n  describe('attributesEquals', () => {\n    it('returns true for same attributes with different chars', () => {\n      const cellA = createStyledCell('A', UnderlineStyle.DOUBLE, 45);\n      const cellB = createStyledCell('B', UnderlineStyle.DOUBLE, 45);\n\n      assert.equal(cellA.attributesEquals(cellB), true);\n    });\n\n    it('detects underline style changes', () => {\n      const cellA = createStyledCell('A', UnderlineStyle.DOUBLE, 45);\n      const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 45);\n\n      assert.equal(cellA.attributesEquals(cellB), false);\n    });\n\n    it('detects underline color changes', () => {\n      const cellA = createStyledCell('A', UnderlineStyle.SINGLE, 45);\n      const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 46);\n\n      assert.equal(cellA.attributesEquals(cellB), false);\n    });\n\n    it('ignores underline variant offsets', () => {\n      const cellA = createStyledCell('A', UnderlineStyle.SINGLE, 45);\n      const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 45);\n      cellA.extended.underlineVariantOffset = 1;\n      cellB.extended.underlineVariantOffset = 3;\n      cellA.updateExtended();\n      cellB.updateExtended();\n\n      assert.equal(cellA.attributesEquals(cellB), true);\n    });\n\n    it('ignores url ids', () => {\n      const cellA = createStyledCell('A', UnderlineStyle.SINGLE, 45);\n      const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 45);\n      cellA.extended.urlId = 1;\n      cellB.extended.urlId = 2;\n      cellA.updateExtended();\n      cellB.updateExtended();\n\n      assert.equal(cellA.attributesEquals(cellB), true);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/buffer/CellData.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CharData, ICellData, IExtendedAttrs } from 'common/Types';\nimport { stringFromCodePoint } from 'common/input/TextDecoder';\nimport { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';\nimport { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';\nimport type { IBufferCell as IBufferCellApi } from '@xterm/xterm';\n\n/**\n * CellData - represents a single Cell in the terminal buffer.\n */\nexport class CellData extends AttributeData implements ICellData {\n  /** Helper to create CellData from CharData. */\n  public static fromCharData(value: CharData): CellData {\n    const obj = new CellData();\n    obj.setFromCharData(value);\n    return obj;\n  }\n  /** Primitives from terminal buffer. */\n  public content = 0;\n  public fg = 0;\n  public bg = 0;\n  public extended: IExtendedAttrs = new ExtendedAttrs();\n  public combinedData = '';\n  /** Whether cell contains a combined string. */\n  public isCombined(): number {\n    return this.content & Content.IS_COMBINED_MASK;\n  }\n  /** Width of the cell. */\n  public getWidth(): number {\n    return this.content >> Content.WIDTH_SHIFT;\n  }\n  /** JS string of the content. */\n  public getChars(): string {\n    if (this.content & Content.IS_COMBINED_MASK) {\n      return this.combinedData;\n    }\n    if (this.content & Content.CODEPOINT_MASK) {\n      return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);\n    }\n    return '';\n  }\n  /**\n   * Codepoint of cell\n   * Note this returns the UTF32 codepoint of single chars,\n   * if content is a combined string it returns the codepoint\n   * of the last char in string to be in line with code in CharData.\n   */\n  public getCode(): number {\n    return (this.isCombined())\n      ? this.combinedData.charCodeAt(this.combinedData.length - 1)\n      : this.content & Content.CODEPOINT_MASK;\n  }\n  /** Set data from CharData */\n  public setFromCharData(value: CharData): void {\n    this.fg = value[CHAR_DATA_ATTR_INDEX];\n    this.bg = 0;\n    let combined = false;\n    // surrogates and combined strings need special treatment\n    if (value[CHAR_DATA_CHAR_INDEX].length > 2) {\n      combined = true;\n    }\n    else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {\n      const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);\n      // if the 2-char string is a surrogate create single codepoint\n      // everything else is combined\n      if (0xD800 <= code && code <= 0xDBFF) {\n        const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);\n        if (0xDC00 <= second && second <= 0xDFFF) {\n          this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n        }\n        else {\n          combined = true;\n        }\n      }\n      else {\n        combined = true;\n      }\n    }\n    else {\n      this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n    }\n    if (combined) {\n      this.combinedData = value[CHAR_DATA_CHAR_INDEX];\n      this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n    }\n  }\n  /** Get data as CharData. */\n  public getAsCharData(): CharData {\n    return [this.fg, this.getChars(), this.getWidth(), this.getCode()];\n  }\n\n  public attributesEquals(other: IBufferCellApi): boolean {\n    if (this.getFgColorMode() !== other.getFgColorMode() || this.getFgColor() !== other.getFgColor()) {\n      return false;\n    }\n    if (this.getBgColorMode() !== other.getBgColorMode() || this.getBgColor() !== other.getBgColor()) {\n      return false;\n    }\n    if (this.isInverse() !== other.isInverse()) {\n      return false;\n    }\n    if (this.isBold() !== other.isBold()) {\n      return false;\n    }\n    if (this.isUnderline() !== other.isUnderline()) {\n      return false;\n    }\n    if (this.isUnderline()) {\n      if (this.getUnderlineStyle() !== other.getUnderlineStyle()) {\n        return false;\n      }\n      const thisDefault = this.isUnderlineColorDefault();\n      const otherDefault = other.isUnderlineColorDefault();\n      if (!(thisDefault && otherDefault)) {\n        if (thisDefault !== otherDefault) {\n          return false;\n        }\n        if (this.getUnderlineColor() !== other.getUnderlineColor()) {\n          return false;\n        }\n        if (this.getUnderlineColorMode() !== other.getUnderlineColorMode()) {\n          return false;\n        }\n      }\n    }\n    if (this.isOverline() !== other.isOverline()) {\n      return false;\n    }\n    if (this.isBlink() !== other.isBlink()) {\n      return false;\n    }\n    if (this.isInvisible() !== other.isInvisible()) {\n      return false;\n    }\n    if (this.isItalic() !== other.isItalic()) {\n      return false;\n    }\n    if (this.isDim() !== other.isDim()) {\n      return false;\n    }\n    if (this.isStrikethrough() !== other.isStrikethrough()) {\n      return false;\n    }\n    return true;\n  }\n\n}\n"
  },
  {
    "path": "src/common/buffer/Constants.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport const DEFAULT_COLOR = 0;\nexport const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);\nexport const DEFAULT_EXT = 0;\n\nexport const CHAR_DATA_ATTR_INDEX = 0;\nexport const CHAR_DATA_CHAR_INDEX = 1;\nexport const CHAR_DATA_WIDTH_INDEX = 2;\nexport const CHAR_DATA_CODE_INDEX = 3;\n\n/**\n * Null cell - a real empty cell (containing nothing).\n * Note that code should always be 0 for a null cell as\n * several test condition of the buffer line rely on this.\n */\nexport const NULL_CELL_CHAR = '';\nexport const NULL_CELL_WIDTH = 1;\nexport const NULL_CELL_CODE = 0;\n\n/**\n * Whitespace cell.\n * This is meant as a replacement for empty cells when needed\n * during rendering lines to preserve correct aligment.\n */\nexport const WHITESPACE_CELL_CHAR = ' ';\nexport const WHITESPACE_CELL_WIDTH = 1;\nexport const WHITESPACE_CELL_CODE = 32;\n\n/**\n * Bitmasks for accessing data in `content`.\n */\nexport const enum Content {\n  /**\n   * bit 1..21    codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)\n   *              read:   `codepoint = content & Content.codepointMask;`\n   *              write:  `content |= codepoint & Content.codepointMask;`\n   *                      shortcut if precondition `codepoint <= 0x10FFFF` is met:\n   *                      `content |= codepoint;`\n   */\n  CODEPOINT_MASK = 0x1FFFFF,\n\n  /**\n   * bit 22       flag indication whether a cell contains combined content\n   *              read:   `isCombined = content & Content.isCombined;`\n   *              set:    `content |= Content.isCombined;`\n   *              clear:  `content &= ~Content.isCombined;`\n   */\n  IS_COMBINED_MASK = 0x200000,  // 1 << 21\n\n  /**\n   * bit 1..22    mask to check whether a cell contains any string data\n   *              we need to check for codepoint and isCombined bits to see\n   *              whether a cell contains anything\n   *              read:   `isEmpty = !(content & Content.hasContent)`\n   */\n  HAS_CONTENT_MASK = 0x3FFFFF,\n\n  /**\n   * bit 23..24   wcwidth value of cell, takes 2 bits (ranges from 0..2)\n   *              read:   `width = (content & Content.widthMask) >> Content.widthShift;`\n   *                      `hasWidth = content & Content.widthMask;`\n   *                      as long as wcwidth is highest value in `content`:\n   *                      `width = content >> Content.widthShift;`\n   *              write:  `content |= (width << Content.widthShift) & Content.widthMask;`\n   *                      shortcut if precondition `0 <= width <= 3` is met:\n   *                      `content |= width << Content.widthShift;`\n   */\n  WIDTH_MASK = 0xC00000,   // 3 << 22\n  WIDTH_SHIFT = 22\n}\n\nexport const enum Attributes {\n  /**\n   * bit 1..8     blue in RGB, color in P256 and P16\n   */\n  BLUE_MASK = 0xFF,\n  BLUE_SHIFT = 0,\n  PCOLOR_MASK = 0xFF,\n  PCOLOR_SHIFT = 0,\n\n  /**\n   * bit 9..16    green in RGB\n   */\n  GREEN_MASK = 0xFF00,\n  GREEN_SHIFT = 8,\n\n  /**\n   * bit 17..24   red in RGB\n   */\n  RED_MASK = 0xFF0000,\n  RED_SHIFT = 16,\n\n  /**\n   * bit 25..26   color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)\n   */\n  CM_MASK = 0x3000000,\n  CM_DEFAULT = 0,\n  CM_P16 = 0x1000000,\n  CM_P256 = 0x2000000,\n  CM_RGB = 0x3000000,\n\n  /**\n   * bit 1..24  RGB room\n   */\n  RGB_MASK = 0xFFFFFF\n}\n\nexport const enum FgFlags {\n  /**\n   * bit 27..32\n   */\n  INVERSE = 0x4000000,\n  BOLD = 0x8000000,\n  UNDERLINE = 0x10000000,\n  BLINK = 0x20000000,\n  INVISIBLE = 0x40000000,\n  STRIKETHROUGH = 0x80000000,\n}\n\nexport const enum BgFlags {\n  /**\n   * bit 27..32 (upper 2 unused)\n   */\n  ITALIC = 0x4000000,\n  DIM = 0x8000000,\n  HAS_EXTENDED = 0x10000000,\n  PROTECTED = 0x20000000,\n  OVERLINE = 0x40000000\n}\n\nexport const enum ExtFlags {\n  /**\n   * bit 27..29\n   */\n  UNDERLINE_STYLE = 0x1C000000,\n\n  /**\n   * bit 30..32\n   *\n   * An optional variant for the glyph, this can be used for example to offset underlines by a\n   * number of pixels to create a perfect pattern.\n   */\n  VARIANT_OFFSET = 0xE0000000\n}\n\nexport const enum UnderlineStyle {\n  NONE = 0,\n  SINGLE = 1,\n  DOUBLE = 2,\n  CURLY = 3,\n  DOTTED = 4,\n  DASHED = 5\n}\n"
  },
  {
    "path": "src/common/buffer/Marker.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable, IMarker } from 'common/Types';\nimport { Emitter } from 'common/Event';\nimport { dispose } from 'common/Lifecycle';\n\nexport class Marker implements IMarker {\n  private static _nextId = 1;\n\n  public isDisposed: boolean = false;\n  private readonly _disposables: IDisposable[] = [];\n\n  private readonly _id: number = Marker._nextId++;\n  public get id(): number { return this._id; }\n\n  private readonly _onDispose = this.register(new Emitter<void>());\n  public readonly onDispose = this._onDispose.event;\n\n  constructor(\n    public line: number\n  ) {\n  }\n\n  public dispose(): void {\n    if (this.isDisposed) {\n      return;\n    }\n    this.isDisposed = true;\n    this.line = -1;\n    // Emit before super.dispose such that dispose listeners get a change to react\n    this._onDispose.fire();\n    dispose(this._disposables);\n    this._disposables.length = 0;\n  }\n\n  public register<T extends IDisposable>(disposable: T): T {\n    this._disposables.push(disposable);\n    return disposable;\n  }\n}\n"
  },
  {
    "path": "src/common/buffer/Types.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset, IDisposable } from 'common/Types';\nimport type { IEvent } from 'common/Event';\n\n// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]\nexport type BufferIndex = [number, number];\n\nexport interface IBuffer {\n  readonly lines: ICircularList<IBufferLine>;\n  ydisp: number;\n  ybase: number;\n  y: number;\n  x: number;\n  tabs: any;\n  scrollBottom: number;\n  scrollTop: number;\n  hasScrollback: boolean;\n  savedY: number;\n  savedX: number;\n  savedCharset: ICharset | undefined;\n  savedCharsets: (ICharset | undefined)[];\n  savedGlevel: number;\n  savedOriginMode: boolean;\n  savedWraparoundMode: boolean;\n  savedCurAttrData: IAttributeData;\n  isCursorInViewport: boolean;\n  markers: IMarker[];\n  translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;\n  getWrappedRangeForLine(y: number): { first: number, last: number };\n  nextStop(x?: number): number;\n  prevStop(x?: number): number;\n  getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine;\n  getNullCell(attr?: IAttributeData): ICellData;\n  getWhitespaceCell(attr?: IAttributeData): ICellData;\n  addMarker(y: number): IMarker;\n  clearMarkers(y: number): void;\n  clearAllMarkers(): void;\n}\n\nexport interface IBufferSet extends IDisposable {\n  alt: IBuffer;\n  normal: IBuffer;\n  active: IBuffer;\n\n  onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>;\n\n  activateNormalBuffer(): void;\n  activateAltBuffer(fillAttr?: IAttributeData): void;\n  reset(): void;\n  resize(newCols: number, newRows: number): void;\n  setupTabStops(i?: number): void;\n}\n"
  },
  {
    "path": "src/common/data/Charsets.ts",
    "content": "/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharset } from 'common/Types';\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: { [key: string]: ICharset | undefined } = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET: ICharset | undefined = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n  '`': '\\u25c6', // '◆'\n  'a': '\\u2592', // '▒'\n  'b': '\\u2409', // '␉' (HT)\n  'c': '\\u240c', // '␌' (FF)\n  'd': '\\u240d', // '␍' (CR)\n  'e': '\\u240a', // '␊' (LF)\n  'f': '\\u00b0', // '°'\n  'g': '\\u00b1', // '±'\n  'h': '\\u2424', // '␤' (NL)\n  'i': '\\u240b', // '␋' (VT)\n  'j': '\\u2518', // '┘'\n  'k': '\\u2510', // '┐'\n  'l': '\\u250c', // '┌'\n  'm': '\\u2514', // '└'\n  'n': '\\u253c', // '┼'\n  'o': '\\u23ba', // '⎺'\n  'p': '\\u23bb', // '⎻'\n  'q': '\\u2500', // '─'\n  'r': '\\u23bc', // '⎼'\n  's': '\\u23bd', // '⎽'\n  't': '\\u251c', // '├'\n  'u': '\\u2524', // '┤'\n  'v': '\\u2534', // '┴'\n  'w': '\\u252c', // '┬'\n  'x': '\\u2502', // '│'\n  'y': '\\u2264', // '≤'\n  'z': '\\u2265', // '≥'\n  '{': '\\u03c0', // 'π'\n  '|': '\\u2260', // '≠'\n  '}': '\\u00a3', // '£'\n  '~': '\\u00b7'  // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n  '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = undefined;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n  '#': '£',\n  '@': '¾',\n  '[': 'ij',\n  '\\\\': '½',\n  ']': '|',\n  '{': '¨',\n  '|': 'f',\n  '}': '¼',\n  '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n  '[': 'Ä',\n  '\\\\': 'Ö',\n  ']': 'Å',\n  '^': 'Ü',\n  '`': 'é',\n  '{': 'ä',\n  '|': 'ö',\n  '}': 'å',\n  '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n  '#': '£',\n  '@': 'à',\n  '[': '°',\n  '\\\\': 'ç',\n  ']': '§',\n  '{': 'é',\n  '|': 'ù',\n  '}': 'è',\n  '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n  '@': 'à',\n  '[': 'â',\n  '\\\\': 'ç',\n  ']': 'ê',\n  '^': 'î',\n  '`': 'ô',\n  '{': 'é',\n  '|': 'ù',\n  '}': 'è',\n  '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n  '@': '§',\n  '[': 'Ä',\n  '\\\\': 'Ö',\n  ']': 'Ü',\n  '{': 'ä',\n  '|': 'ö',\n  '}': 'ü',\n  '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n  '#': '£',\n  '@': '§',\n  '[': '°',\n  '\\\\': 'ç',\n  ']': 'é',\n  '`': 'ù',\n  '{': 'à',\n  '|': 'ò',\n  '}': 'è',\n  '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n  '@': 'Ä',\n  '[': 'Æ',\n  '\\\\': 'Ø',\n  ']': 'Å',\n  '^': 'Ü',\n  '`': 'ä',\n  '{': 'æ',\n  '|': 'ø',\n  '}': 'å',\n  '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n  '#': '£',\n  '@': '§',\n  '[': '¡',\n  '\\\\': 'Ñ',\n  ']': '¿',\n  '{': '°',\n  '|': 'ñ',\n  '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n  '@': 'É',\n  '[': 'Ä',\n  '\\\\': 'Ö',\n  ']': 'Å',\n  '^': 'Ü',\n  '`': 'é',\n  '{': 'ä',\n  '|': 'ö',\n  '}': 'å',\n  '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n  '#': 'ù',\n  '@': 'à',\n  '[': 'é',\n  '\\\\': 'ç',\n  ']': 'ê',\n  '^': 'î',\n\n  '_': 'è',\n  '`': 'ô',\n  '{': 'ä',\n  '|': 'ö',\n  '}': 'ü',\n  '~': 'û'\n};\n"
  },
  {
    "path": "src/common/data/EscapeSequences.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport const enum C0 {\n  /** Null (Caret = ^@, C = \\0) */\n  NUL = '\\x00',\n  /** Start of Heading (Caret = ^A) */\n  SOH = '\\x01',\n  /** Start of Text (Caret = ^B) */\n  STX = '\\x02',\n  /** End of Text (Caret = ^C) */\n  ETX = '\\x03',\n  /** End of Transmission (Caret = ^D) */\n  EOT = '\\x04',\n  /** Enquiry (Caret = ^E) */\n  ENQ = '\\x05',\n  /** Acknowledge (Caret = ^F) */\n  ACK = '\\x06',\n  /** Bell (Caret = ^G, C = \\a) */\n  BEL = '\\x07',\n  /** Backspace (Caret = ^H, C = \\b) */\n  BS = '\\x08',\n  /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n  HT = '\\x09',\n  /** Line Feed (Caret = ^J, C = \\n) */\n  LF = '\\x0a',\n  /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n  VT = '\\x0b',\n  /** Form Feed (Caret = ^L, C = \\f) */\n  FF = '\\x0c',\n  /** Carriage Return (Caret = ^M, C = \\r) */\n  CR = '\\x0d',\n  /** Shift Out (Caret = ^N) */\n  SO = '\\x0e',\n  /** Shift In (Caret = ^O) */\n  SI = '\\x0f',\n  /** Data Link Escape (Caret = ^P) */\n  DLE = '\\x10',\n  /** Device Control One (XON) (Caret = ^Q) */\n  DC1 = '\\x11',\n  /** Device Control Two (Caret = ^R) */\n  DC2 = '\\x12',\n  /** Device Control Three (XOFF) (Caret = ^S) */\n  DC3 = '\\x13',\n  /** Device Control Four (Caret = ^T) */\n  DC4 = '\\x14',\n  /** Negative Acknowledge (Caret = ^U) */\n  NAK = '\\x15',\n  /** Synchronous Idle (Caret = ^V) */\n  SYN = '\\x16',\n  /** End of Transmission Block (Caret = ^W) */\n  ETB = '\\x17',\n  /** Cancel (Caret = ^X) */\n  CAN = '\\x18',\n  /** End of Medium (Caret = ^Y) */\n  EM = '\\x19',\n  /** Substitute (Caret = ^Z) */\n  SUB = '\\x1a',\n  /** Escape (Caret = ^[, C = \\e) */\n  ESC = '\\x1b',\n  /** File Separator (Caret = ^\\) */\n  FS = '\\x1c',\n  /** Group Separator (Caret = ^]) */\n  GS = '\\x1d',\n  /** Record Separator (Caret = ^^) */\n  RS = '\\x1e',\n  /** Unit Separator (Caret = ^_) */\n  US = '\\x1f',\n  /** Space */\n  SP = '\\x20',\n  /** Delete (Caret = ^?) */\n  DEL = '\\x7f'\n}\n\n/**\n * C1 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport const enum C1 {\n  /** padding character */\n  PAD = '\\x80',\n  /** High Octet Preset */\n  HOP = '\\x81',\n  /** Break Permitted Here */\n  BPH = '\\x82',\n  /** No Break Here */\n  NBH = '\\x83',\n  /** Index */\n  IND = '\\x84',\n  /** Next Line */\n  NEL = '\\x85',\n  /** Start of Selected Area */\n  SSA = '\\x86',\n  /** End of Selected Area */\n  ESA = '\\x87',\n  /** Horizontal Tabulation Set */\n  HTS = '\\x88',\n  /** Horizontal Tabulation With Justification */\n  HTJ = '\\x89',\n  /** Vertical Tabulation Set */\n  VTS = '\\x8a',\n  /** Partial Line Down */\n  PLD = '\\x8b',\n  /** Partial Line Up */\n  PLU = '\\x8c',\n  /** Reverse Index */\n  RI = '\\x8d',\n  /** Single-Shift 2 */\n  SS2 = '\\x8e',\n  /** Single-Shift 3 */\n  SS3 = '\\x8f',\n  /** Device Control String */\n  DCS = '\\x90',\n  /** Private Use 1 */\n  PU1 = '\\x91',\n  /** Private Use 2 */\n  PU2 = '\\x92',\n  /** Set Transmit State */\n  STS = '\\x93',\n  /** Destructive backspace, intended to eliminate ambiguity about meaning of BS. */\n  CCH = '\\x94',\n  /** Message Waiting */\n  MW = '\\x95',\n  /** Start of Protected Area */\n  SPA = '\\x96',\n  /** End of Protected Area */\n  EPA = '\\x97',\n  /** Start of String */\n  SOS = '\\x98',\n  /** Single Graphic Character Introducer */\n  SGCI = '\\x99',\n  /** Single Character Introducer */\n  SCI = '\\x9a',\n  /** Control Sequence Introducer */\n  CSI = '\\x9b',\n  /** String Terminator */\n  ST = '\\x9c',\n  /** Operating System Command */\n  OSC = '\\x9d',\n  /** Privacy Message */\n  PM = '\\x9e',\n  /** Application Program Command */\n  APC = '\\x9f'\n}\n\nexport const enum C1ESCAPED {\n  ST = '\\x1b\\\\'\n}\n"
  },
  {
    "path": "src/common/input/Keyboard.test.ts",
    "content": "\nimport { assert } from 'chai';\nimport { evaluateKeyboardEvent } from 'common/input/Keyboard';\nimport { IKeyboardResult, IKeyboardEvent } from 'common/Types';\n\n/**\n * A helper function for testing which allows passing in a partial event and defaults will be filled\n * in on it.\n */\nfunction testEvaluateKeyboardEvent(partialEvent: {\n  altKey?: boolean;\n  ctrlKey?: boolean;\n  shiftKey?: boolean;\n  metaKey?: boolean;\n  keyCode?: number;\n  code?: string;\n  key?: string;\n  type?: string;\n}, partialOptions: {\n  applicationCursorMode?: boolean;\n  isMac?: boolean;\n  macOptionIsMeta?: boolean;\n} = {}): IKeyboardResult {\n  const event: IKeyboardEvent = {\n    altKey: partialEvent.altKey || false,\n    ctrlKey: partialEvent.ctrlKey || false,\n    shiftKey: partialEvent.shiftKey || false,\n    metaKey: partialEvent.metaKey || false,\n    keyCode: partialEvent.keyCode ?? 0,\n    code: partialEvent.code || '',\n    key: partialEvent.key || '',\n    type: partialEvent.type || ''\n  };\n  const options = {\n    applicationCursorMode: partialOptions.applicationCursorMode || false,\n    isMac: partialOptions.isMac || false,\n    macOptionIsMeta: partialOptions.macOptionIsMeta || false\n  };\n  return evaluateKeyboardEvent(event, options.applicationCursorMode, options.isMac, options.macOptionIsMeta);\n}\n\ndescribe('Keyboard', () => {\n  describe('evaluateKeyEscapeSequence', () => {\n    it('should return the correct escape sequence for unmodified keys', () => {\n      // Backspace\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 8 }).key, '\\x7f'); // ^?\n      // Tab\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 9 }).key, '\\t');\n      // Return/enter\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 13 }).key, '\\r'); // CR\n      // Escape\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 27 }).key, '\\x1b');\n      // Page up, page down\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 33 }).key, '\\x1b[5~'); // CSI 5 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 34 }).key, '\\x1b[6~'); // CSI 6 ~\n      // End, Home\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 35 }).key, '\\x1b[F'); // SS3 F\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 36 }).key, '\\x1b[H'); // SS3 H\n      // Left, up, right, down arrows\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 37 }).key, '\\x1b[D'); // CSI D\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 38 }).key, '\\x1b[A'); // CSI A\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 39 }).key, '\\x1b[C'); // CSI C\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 40 }).key, '\\x1b[B'); // CSI B\n      // Insert\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 45 }).key, '\\x1b[2~'); // CSI 2 ~\n      // Delete\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 46 }).key, '\\x1b[3~'); // CSI 3 ~\n      // F1-F12\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 112 }).key, '\\x1bOP'); // SS3 P\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 113 }).key, '\\x1bOQ'); // SS3 Q\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 114 }).key, '\\x1bOR'); // SS3 R\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 115 }).key, '\\x1bOS'); // SS3 S\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 116 }).key, '\\x1b[15~'); // CSI 1 5 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 117 }).key, '\\x1b[17~'); // CSI 1 7 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 118 }).key, '\\x1b[18~'); // CSI 1 8 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 119 }).key, '\\x1b[19~'); // CSI 1 9 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 120 }).key, '\\x1b[20~'); // CSI 2 0 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 121 }).key, '\\x1b[21~'); // CSI 2 1 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 122 }).key, '\\x1b[23~'); // CSI 2 3 ~\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 123 }).key, '\\x1b[24~'); // CSI 2 4 ~\n    });\n    it('should return \\\\x1b[3;5~ for ctrl+delete', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 46 }).key, '\\x1b[3;5~');\n    });\n    it('should return \\\\x1b[3;2~ for shift+delete', () => {\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\\x1b[3;2~');\n    });\n    it('should return \\\\x1b[3;3~ for alt+delete', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\\x1b[3;3~');\n    });\n    it('should return \\\\x1b\\\\r for alt+enter', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 13 }).key, '\\x1b\\r');\n    });\n    it('should return \\\\x1b\\\\x1b for alt+esc', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 27 }).key, '\\x1b\\x1b');\n    });\n    it('should return \\\\x1b[5D for ctrl+left', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 37 }).key, '\\x1b[1;5D'); // CSI 5 D\n    });\n    it('should return \\\\x1b[5C for ctrl+right', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 39 }).key, '\\x1b[1;5C'); // CSI 5 C\n    });\n    it('should return \\\\x1b[5A for ctrl+up', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 38 }).key, '\\x1b[1;5A'); // CSI 5 A\n    });\n    it('should return \\\\x1b[5B for ctrl+down', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 40 }).key, '\\x1b[1;5B'); // CSI 5 B\n    });\n    it('should return \\\\x08 for ctrl+backspace', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 8 }).key, '\\x08');\n    });\n    it('should return \\\\x1b\\\\x7f for alt+backspace', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 8 }).key, '\\x1b\\x7f');\n    });\n    it('should return \\\\x1b\\\\x08 for ctrl+alt+backspace', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, altKey: true, keyCode: 8 }).key, '\\x1b\\x08');\n    });\n    it('should return \\\\x1b[3;2~ for shift+delete', () => {\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\\x1b[3;2~');\n    });\n    it('should return \\\\x1b[3;3~ for alt+delete', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\\x1b[3;3~');\n    });\n\n    describe('On non-macOS platforms', () => {\n      // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100\n      // http://unix.stackexchange.com/a/108106\n      it('should return \\\\x1b[1;3D for alt+left', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: false }).key, '\\x1b[1;3D'); // CSI 1;3 D\n      });\n      it('should return \\\\x1b[1;3C for alt+right', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\\x1b[1;3C'); // CSI 1;3 C\n      });\n      it('should return \\\\x1b[1;3A for alt+up', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\\x1b[1;3A'); // CSI 1;3 A\n      });\n      it('should return \\\\x1b[1;3B for alt+down', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\\x1b[1;3B'); // CSI 1;3 B\n      });\n      it('should return \\\\x1ba for alt+a', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\\x1ba');\n      });\n      it('should return \\\\x1b\\\\x20 for alt+space', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 32 }, { isMac: false }).key, '\\x1b\\x20');\n      });\n      it('should return \\\\x1b\\\\x00 for ctrl+alt+space', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, ctrlKey: true, keyCode: 32 }, { isMac: false }).key, '\\x1b\\x00');\n      });\n    });\n\n    describe('On macOS platforms', () => {\n      it('should return \\\\x1b[1;3D for alt+left', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: true }).key, '\\x1b[1;3D'); // CSI 1;3 D\n      });\n      it('should return \\\\x1b[1;3C for alt+right', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\\x1b[1;3C'); // CSI 1;3 C\n      });\n      it('should return \\\\x1b[1;3A for alt+up', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\\x1b[1;3A'); // CSI 1;3 A\n      });\n      it('should return \\\\x1b[1;3B for alt+down', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\\x1b[1;3B'); // CSI 1;3 B\n      });\n      it('should return undefined for alt+a', () => {\n        assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined);\n      });\n    });\n\n    describe('with macOptionIsMeta', () => {\n      it('should return \\\\x1ba for alt+a', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true, macOptionIsMeta: true }).key, '\\x1ba');\n      });\n\n      it('should return \\\\x1b\\\\x1b for alt+enter', () => {\n        assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 13 }, { isMac: true, macOptionIsMeta: true }).key, '\\x1b\\r');\n      });\n    });\n\n    it('should return \\\\x1b[1;3A for alt+up', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\\x1b[1;3A'); // CSI 1;3 A\n    });\n    it('should return \\\\x1b[1;3B for alt+down', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\\x1b[1;3B'); // CSI 1;3 B\n    });\n    it('should return the correct escape sequence for modified F1-F12 keys', () => {\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 112 }).key, '\\x1b[1;2P');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 113 }).key, '\\x1b[1;2Q');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 114 }).key, '\\x1b[1;2R');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 115 }).key, '\\x1b[1;2S');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 116 }).key, '\\x1b[15;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 117 }).key, '\\x1b[17;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 118 }).key, '\\x1b[18;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 119 }).key, '\\x1b[19;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 120 }).key, '\\x1b[20;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 121 }).key, '\\x1b[21;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 122 }).key, '\\x1b[23;2~');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 123 }).key, '\\x1b[24;2~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 112 }).key, '\\x1b[1;3P');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 113 }).key, '\\x1b[1;3Q');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 114 }).key, '\\x1b[1;3R');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 115 }).key, '\\x1b[1;3S');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 116 }).key, '\\x1b[15;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 117 }).key, '\\x1b[17;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 118 }).key, '\\x1b[18;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 119 }).key, '\\x1b[19;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 120 }).key, '\\x1b[20;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 121 }).key, '\\x1b[21;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 122 }).key, '\\x1b[23;3~');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 123 }).key, '\\x1b[24;3~');\n\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 112 }).key, '\\x1b[1;5P');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 113 }).key, '\\x1b[1;5Q');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 114 }).key, '\\x1b[1;5R');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 115 }).key, '\\x1b[1;5S');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 116 }).key, '\\x1b[15;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 117 }).key, '\\x1b[17;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 118 }).key, '\\x1b[18;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 119 }).key, '\\x1b[19;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 120 }).key, '\\x1b[20;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 121 }).key, '\\x1b[21;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 122 }).key, '\\x1b[23;5~');\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 123 }).key, '\\x1b[24;5~');\n    });\n\n    // Characters using ctrl+alt sequences\n    it('should return proper sequence for ctrl+alt+a', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\\x1b\\x01');\n    });\n\n    // Characters using alt sequences (numbers)\n    it('should return proper sequences for alt+0', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 48 }).key, '\\x1b0');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 48 }).key, '\\x1b)');\n    });\n    it('should return proper sequences for alt+1', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 49 }).key, '\\x1b1');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 49 }).key, '\\x1b!');\n    });\n    it('should return proper sequences for alt+2', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 50 }).key, '\\x1b2');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 50 }).key, '\\x1b@');\n    });\n    it('should return proper sequences for alt+3', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 51 }).key, '\\x1b3');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 51 }).key, '\\x1b#');\n    });\n    it('should return proper sequences for alt+4', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 52 }).key, '\\x1b4');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 52 }).key, '\\x1b$');\n    });\n    it('should return proper sequences for alt+5', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 53 }).key, '\\x1b5');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 53 }).key, '\\x1b%');\n    });\n    it('should return proper sequences for alt+6', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 54 }).key, '\\x1b6');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 54 }).key, '\\x1b^');\n    });\n    it('should return proper sequences for alt+7', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 55 }).key, '\\x1b7');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 55 }).key, '\\x1b&');\n    });\n    it('should return proper sequences for alt+8', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 56 }).key, '\\x1b8');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 56 }).key, '\\x1b*');\n    });\n    it('should return proper sequences for alt+9', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 57 }).key, '\\x1b9');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 57 }).key, '\\x1b(');\n    });\n\n    // Characters using alt sequences (special chars)\n    it('should return proper sequences for alt+;', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\\x1b;');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 186 }).key, '\\x1b:');\n    });\n    it('should return proper sequences for alt+=', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 187 }).key, '\\x1b=');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 187 }).key, '\\x1b+');\n    });\n    it('should return proper sequences for alt+,', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 188 }).key, '\\x1b,');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 188 }).key, '\\x1b<');\n    });\n    it('should return proper sequences for alt+-', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 189 }).key, '\\x1b-');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 189 }).key, '\\x1b_');\n    });\n    it('should return proper sequences for alt+.', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\\x1b.');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 190 }).key, '\\x1b>');\n    });\n    it('should return proper sequences for alt+/', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 191 }).key, '\\x1b/');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 191 }).key, '\\x1b?');\n    });\n    it('should return proper sequences for alt+~', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\\x1b`');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\\x1b~');\n    });\n    it('should return proper sequences for alt+[', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\\x1b[');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 219 }).key, '\\x1b{');\n    });\n    it('should return proper sequences for alt+\\\\', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 220 }).key, '\\x1b\\\\');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 220 }).key, '\\x1b|');\n    });\n    it('should return proper sequences for alt+]', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\\x1b]');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 221 }).key, '\\x1b}');\n    });\n    it('should return proper sequences for alt+\\'', () => {\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\\x1b\\'');\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true,  keyCode: 222 }).key, '\\x1b\"');\n    });\n\n    it('should handle mobile arrow events', () => {\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputUpArrow' }).key, '\\x1b[A');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputUpArrow' }, { applicationCursorMode: true }).key, '\\x1bOA');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputLeftArrow' }).key, '\\x1b[D');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputLeftArrow' }, { applicationCursorMode: true }).key, '\\x1bOD');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputRightArrow' }).key, '\\x1b[C');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputRightArrow' }, { applicationCursorMode: true }).key, '\\x1bOC');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }).key, '\\x1b[B');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }, { applicationCursorMode: true }).key, '\\x1bOB');\n    });\n\n    it('should handle lowercase letters', () => {\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 65, key: 'a' }).key, 'a');\n      assert.equal(testEvaluateKeyboardEvent({ keyCode: 189, key: '-' }).key, '-');\n    });\n\n    it('should handle uppercase letters', () => {\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 65, key: 'A' }).key, 'A');\n      assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 49, key: '!' }).key, '!');\n    });\n\n    // Characters using alt+shift sequences (letters)\n    it('should return proper sequences for alt+shift+letter combinations', () => {\n      // Test alt+shift combinations produce uppercase letters\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 65 }).key, '\\x1bA'); // alt+shift+a\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 72 }).key, '\\x1bH'); // alt+shift+h\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 90 }).key, '\\x1bZ'); // alt+shift+z\n\n      // Test alt without shift produces lowercase letters\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 65 }).key, '\\x1ba'); // alt+a\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 72 }).key, '\\x1bh'); // alt+h\n      assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 90 }).key, '\\x1bz'); // alt+z\n    });\n\n    it('should return proper sequence for ctrl+@', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, shiftKey: true, keyCode: 50, code: 'Digit2', key: '@' }).key, '\\x00');\n    });\n\n    it('should return proper sequence for ctrl+^', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, shiftKey: true, keyCode: 54, code: 'Digit6', key: '^' }).key, '\\x1e');\n    });\n\n    it('should return proper sequence for ctrl+_', () => {\n      assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, shiftKey: true, keyCode: 189, code: 'Minus', key: '_' }).key, '\\x1f');\n    });\n\n  });\n});\n"
  },
  {
    "path": "src/common/input/Keyboard.ts",
    "content": "/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n */\n\nimport { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types';\nimport { C0 } from 'common/data/EscapeSequences';\n\n// reg + shift key mappings for digits and special chars\nconst KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = {\n  // digits 0-9\n  48: ['0', ')'],\n  49: ['1', '!'],\n  50: ['2', '@'],\n  51: ['3', '#'],\n  52: ['4', '$'],\n  53: ['5', '%'],\n  54: ['6', '^'],\n  55: ['7', '&'],\n  56: ['8', '*'],\n  57: ['9', '('],\n\n  // special chars\n  186: [';', ':'],\n  187: ['=', '+'],\n  188: [',', '<'],\n  189: ['-', '_'],\n  190: ['.', '>'],\n  191: ['/', '?'],\n  192: ['`', '~'],\n  219: ['[', '{'],\n  220: ['\\\\', '|'],\n  221: [']', '}'],\n  222: ['\\'', '\"']\n};\n\nexport function evaluateKeyboardEvent(\n  ev: IKeyboardEvent,\n  applicationCursorMode: boolean,\n  isMac: boolean,\n  macOptionIsMeta: boolean\n): IKeyboardResult {\n  const result: IKeyboardResult = {\n    type: KeyboardResultType.SEND_KEY,\n    // Whether to cancel event propagation (NOTE: this may not be needed since the event is\n    // canceled at the end of keyDown\n    cancel: false,\n    // The new key even to emit\n    key: undefined\n  };\n  const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);\n  switch (ev.keyCode) {\n    case 0:\n      if (ev.key === 'UIKeyInputUpArrow') {\n        if (applicationCursorMode) {\n          result.key = C0.ESC + 'OA';\n        } else {\n          result.key = C0.ESC + '[A';\n        }\n      }\n      else if (ev.key === 'UIKeyInputLeftArrow') {\n        if (applicationCursorMode) {\n          result.key = C0.ESC + 'OD';\n        } else {\n          result.key = C0.ESC + '[D';\n        }\n      }\n      else if (ev.key === 'UIKeyInputRightArrow') {\n        if (applicationCursorMode) {\n          result.key = C0.ESC + 'OC';\n        } else {\n          result.key = C0.ESC + '[C';\n        }\n      }\n      else if (ev.key === 'UIKeyInputDownArrow') {\n        if (applicationCursorMode) {\n          result.key = C0.ESC + 'OB';\n        } else {\n          result.key = C0.ESC + '[B';\n        }\n      }\n      break;\n    case 8:\n      // backspace\n      result.key = ev.ctrlKey ? '\\b' : C0.DEL; // ^H or ^?\n      if (ev.altKey) {\n        result.key = C0.ESC + result.key;\n      }\n      break;\n    case 9:\n      // tab\n      if (ev.shiftKey) {\n        result.key = C0.ESC + '[Z';\n        break;\n      }\n      result.key = C0.HT;\n      result.cancel = true;\n      break;\n    case 13:\n      // return/enter\n      if (ev.key === 'c' && ev.ctrlKey) {\n        // HACK: Safari on iPad, iOS, AppleVisionPro sends key 13 when typing ctrl-c on hardware\n        // keyboard\n        result.key = C0.ETX;\n      } else {\n        result.key = ev.altKey ? C0.ESC + C0.CR : C0.CR;\n      }\n      result.cancel = true;\n      break;\n    case 27:\n      // escape\n      result.key = C0.ESC;\n      if (ev.altKey) {\n        result.key = C0.ESC + C0.ESC;\n      }\n      result.cancel = true;\n      break;\n    case 37:\n      // left-arrow\n      if (ev.metaKey) {\n        break;\n      }\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n      } else if (applicationCursorMode) {\n        result.key = C0.ESC + 'OD';\n      } else {\n        result.key = C0.ESC + '[D';\n      }\n      break;\n    case 39:\n      // right-arrow\n      if (ev.metaKey) {\n        break;\n      }\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n      } else if (applicationCursorMode) {\n        result.key = C0.ESC + 'OC';\n      } else {\n        result.key = C0.ESC + '[C';\n      }\n      break;\n    case 38:\n      // up-arrow\n      if (ev.metaKey) {\n        break;\n      }\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n      } else if (applicationCursorMode) {\n        result.key = C0.ESC + 'OA';\n      } else {\n        result.key = C0.ESC + '[A';\n      }\n      break;\n    case 40:\n      // down-arrow\n      if (ev.metaKey) {\n        break;\n      }\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n      } else if (applicationCursorMode) {\n        result.key = C0.ESC + 'OB';\n      } else {\n        result.key = C0.ESC + '[B';\n      }\n      break;\n    case 45:\n      // insert\n      if (!ev.shiftKey && !ev.ctrlKey) {\n        // <Ctrl> or <Shift> + <Insert> are used to\n        // copy-paste on some systems.\n        result.key = C0.ESC + '[2~';\n      }\n      break;\n    case 46:\n      // delete\n      if (modifiers) {\n        result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[3~';\n      }\n      break;\n    case 36:\n      // home\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n      } else if (applicationCursorMode) {\n        result.key = C0.ESC + 'OH';\n      } else {\n        result.key = C0.ESC + '[H';\n      }\n      break;\n    case 35:\n      // end\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n      } else if (applicationCursorMode) {\n        result.key = C0.ESC + 'OF';\n      } else {\n        result.key = C0.ESC + '[F';\n      }\n      break;\n    case 33:\n      // page up\n      if (ev.shiftKey) {\n        result.type = KeyboardResultType.PAGE_UP;\n      } else if (ev.ctrlKey) {\n        result.key = C0.ESC + '[5;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[5~';\n      }\n      break;\n    case 34:\n      // page down\n      if (ev.shiftKey) {\n        result.type = KeyboardResultType.PAGE_DOWN;\n      } else if (ev.ctrlKey) {\n        result.key = C0.ESC + '[6;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[6~';\n      }\n      break;\n    case 112:\n      // F1-F12\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n      } else {\n        result.key = C0.ESC + 'OP';\n      }\n      break;\n    case 113:\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n      } else {\n        result.key = C0.ESC + 'OQ';\n      }\n      break;\n    case 114:\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n      } else {\n        result.key = C0.ESC + 'OR';\n      }\n      break;\n    case 115:\n      if (modifiers) {\n        result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n      } else {\n        result.key = C0.ESC + 'OS';\n      }\n      break;\n    case 116:\n      if (modifiers) {\n        result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[15~';\n      }\n      break;\n    case 117:\n      if (modifiers) {\n        result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[17~';\n      }\n      break;\n    case 118:\n      if (modifiers) {\n        result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[18~';\n      }\n      break;\n    case 119:\n      if (modifiers) {\n        result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[19~';\n      }\n      break;\n    case 120:\n      if (modifiers) {\n        result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[20~';\n      }\n      break;\n    case 121:\n      if (modifiers) {\n        result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[21~';\n      }\n      break;\n    case 122:\n      if (modifiers) {\n        result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[23~';\n      }\n      break;\n    case 123:\n      if (modifiers) {\n        result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n      } else {\n        result.key = C0.ESC + '[24~';\n      }\n      break;\n    default:\n      // a-z and space\n      if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n        if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n          result.key = String.fromCharCode(ev.keyCode - 64);\n        } else if (ev.keyCode === 32) {\n          result.key = C0.NUL;\n        } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n          // escape, file sep, group sep, record sep, unit sep\n          result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n        } else if (ev.keyCode === 56) {\n          result.key = C0.DEL;\n        } else if (ev.key === '/') {\n          result.key = C0.US; // https://github.com/xtermjs/xterm.js/issues/5457\n        } else if (ev.keyCode === 219) {\n          result.key = C0.ESC;\n        } else if (ev.keyCode === 220) {\n          result.key = C0.FS;\n        } else if (ev.keyCode === 221) {\n          result.key = C0.GS;\n        }\n      } else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {\n        // On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.\n        const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode];\n        const key = keyMapping?.[!ev.shiftKey ? 0 : 1];\n        if (key) {\n          result.key = C0.ESC + key;\n        } else if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n          const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32;\n          let keyString = String.fromCharCode(keyCode);\n          if (ev.shiftKey) {\n            keyString = keyString.toUpperCase();\n          }\n          result.key = C0.ESC + keyString;\n        } else if (ev.keyCode === 32) {\n          result.key = C0.ESC + (ev.ctrlKey ? C0.NUL : ' ');\n        } else if (ev.key === 'Dead' && ev.code.startsWith('Key')) {\n          // Reference: https://github.com/xtermjs/xterm.js/issues/3725\n          // Alt will produce a \"dead key\" (initate composition) with some\n          // of the letters in US layout (e.g. N/E/U).\n          // It's safe to match against Key* since no other `code` values begin with \"Key\".\n          // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values#code_values_on_mac\n          let keyString = ev.code.slice(3, 4);\n          if (!ev.shiftKey) {\n            keyString = keyString.toLowerCase();\n          }\n          result.key = C0.ESC + keyString;\n          result.cancel = true;\n        }\n      } else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) {\n        if (ev.keyCode === 65) { // cmd + a\n          result.type = KeyboardResultType.SELECT_ALL;\n        }\n      } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) {\n        // Include only keys that that result in a _single_ character; don't include num lock,\n        // volume up, etc.\n        result.key = ev.key;\n      } else if (ev.key && ev.ctrlKey && ev.shiftKey) {\n        switch (ev.code) {\n          case 'Minus':  result.key = C0.US;  break; // ^_ (Ctrl+Shift+-_\n          case 'Digit2': result.key = C0.NUL; break; // ^@ (Ctrl+Shift+2)\n          case 'Digit6': result.key = C0.RS;  break; // ^^ (Ctrl+Shift+6)\n        }\n      }\n      break;\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "src/common/input/KittyKeyboard.test.ts",
    "content": "\nimport { assert } from 'chai';\nimport { KittyKeyboard, KittyKeyboardEventType, KittyKeyboardFlags } from 'common/input/KittyKeyboard';\nimport { IKeyboardEvent } from 'common/Types';\n\nfunction createEvent(partialEvent: Partial<IKeyboardEvent> = {}): IKeyboardEvent {\n  return {\n    altKey: partialEvent.altKey || false,\n    ctrlKey: partialEvent.ctrlKey || false,\n    shiftKey: partialEvent.shiftKey || false,\n    metaKey: partialEvent.metaKey || false,\n    keyCode: partialEvent.keyCode ?? 0,\n    code: partialEvent.code || '',\n    key: partialEvent.key || '',\n    type: partialEvent.type || 'keydown'\n  };\n}\n\ndescribe('KittyKeyboard', () => {\n  let kitty: KittyKeyboard;\n\n  beforeEach(() => {\n    kitty = new KittyKeyboard();\n  });\n\n  describe('shouldUseProtocol', () => {\n    it('should return false when flags are 0', () => {\n      assert.strictEqual(KittyKeyboard.shouldUseProtocol(0), false);\n    });\n\n    it('should return true when any flag is set', () => {\n      assert.strictEqual(KittyKeyboard.shouldUseProtocol(KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES), true);\n      assert.strictEqual(KittyKeyboard.shouldUseProtocol(KittyKeyboardFlags.REPORT_EVENT_TYPES), true);\n      assert.strictEqual(KittyKeyboard.shouldUseProtocol(0b11111), true);\n    });\n  });\n\n  describe('evaluate', () => {\n    describe('modifier encoding (value = 1 + modifiers)', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('shift+letter sends plain character in DISAMBIGUATE mode', () => {\n        // Kitty spec: DISAMBIGUATE only encodes keys ambiguous in legacy encoding\n        // Shift+a → \"A\" is not ambiguous, so send plain \"A\"\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);\n        assert.strictEqual(result.key, 'A');\n      });\n\n      it('alt=3 (1+2) still uses CSI u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', altKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;3u');\n      });\n\n      it('ctrl=5 (1+4)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;5u');\n      });\n\n      it('super/meta=9 (1+8)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', metaKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;9u');\n      });\n\n      it('ctrl+shift=6 (1+4+1)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;6u');\n      });\n\n      it('ctrl+alt=7 (1+4+2)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, altKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;7u');\n      });\n\n      it('ctrl+alt+shift=8 (1+4+2+1)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, altKey: true, shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;8u');\n      });\n\n      it('ctrl+super=13 (1+4+8)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, metaKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;13u');\n      });\n\n      it('all four modifiers=16 (1+1+2+4+8)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', shiftKey: true, altKey: true, ctrlKey: true, metaKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;16u');\n      });\n\n      it('no modifiers omits modifier field', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags);\n        assert.strictEqual(result.key, '\\x1b[27u');\n      });\n    });\n\n    describe('C0 control keys with DISAMBIGUATE_ESCAPE_CODES', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('Escape → CSI 27 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags);\n        assert.strictEqual(result.key, '\\x1b[27u');\n      });\n\n      it('Enter → legacy \\\\r', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags);\n        assert.strictEqual(result.key, '\\r');\n      });\n\n      it('Tab → legacy \\\\t', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags);\n        assert.strictEqual(result.key, '\\t');\n      });\n\n      it('Backspace → legacy \\\\x7f', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags);\n        assert.strictEqual(result.key, '\\x7f');\n      });\n\n      it('Space → plain space (text-generating key)', () => {\n        const result = kitty.evaluate(createEvent({ key: ' ' }), flags);\n        assert.strictEqual(result.key, ' ');\n      });\n\n      it('Shift+Tab → CSI 9;2 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Tab', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[9;2u');\n      });\n\n      it('Ctrl+Enter → CSI 13;5 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Enter', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[13;5u');\n      });\n\n      it('Alt+Escape → CSI 27;3 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Escape', altKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[27;3u');\n      });\n\n      it('Ctrl+Backspace → CSI 127;5 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Backspace', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[127;5u');\n      });\n\n      it('Ctrl+Space → CSI 32;5 u', () => {\n        const result = kitty.evaluate(createEvent({ key: ' ', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[32;5u');\n      });\n\n      it('Alt+Space → CSI 32;3 u', () => {\n        const result = kitty.evaluate(createEvent({ key: ' ', altKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[32;3u');\n      });\n    });\n\n    describe('navigation keys', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('Insert → CSI 2 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Insert' }), flags);\n        assert.strictEqual(result.key, '\\x1b[2~');\n      });\n\n      it('Delete → CSI 3 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Delete' }), flags);\n        assert.strictEqual(result.key, '\\x1b[3~');\n      });\n\n      it('PageUp → CSI 5 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'PageUp' }), flags);\n        assert.strictEqual(result.key, '\\x1b[5~');\n      });\n\n      it('PageDown → CSI 6 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'PageDown' }), flags);\n        assert.strictEqual(result.key, '\\x1b[6~');\n      });\n\n      it('Home → CSI H', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Home' }), flags);\n        assert.strictEqual(result.key, '\\x1b[H');\n      });\n\n      it('End → CSI F', () => {\n        const result = kitty.evaluate(createEvent({ key: 'End' }), flags);\n        assert.strictEqual(result.key, '\\x1b[F');\n      });\n\n      it('Shift+PageUp → CSI 5;2 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'PageUp', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[5;2~');\n      });\n\n      it('Ctrl+Home → CSI 1;5 H', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Home', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[1;5H');\n      });\n    });\n\n    describe('arrow keys', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('ArrowUp → CSI A', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowUp' }), flags);\n        assert.strictEqual(result.key, '\\x1b[A');\n      });\n\n      it('ArrowDown → CSI B', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowDown' }), flags);\n        assert.strictEqual(result.key, '\\x1b[B');\n      });\n\n      it('ArrowRight → CSI C', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowRight' }), flags);\n        assert.strictEqual(result.key, '\\x1b[C');\n      });\n\n      it('ArrowLeft → CSI D', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowLeft' }), flags);\n        assert.strictEqual(result.key, '\\x1b[D');\n      });\n\n      it('Shift+ArrowUp → CSI 1;2 A', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowUp', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[1;2A');\n      });\n\n      it('Ctrl+ArrowLeft → CSI 1;5 D', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowLeft', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[1;5D');\n      });\n\n      it('Ctrl+Shift+ArrowRight → CSI 1;6 C', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ArrowRight', ctrlKey: true, shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[1;6C');\n      });\n    });\n\n    describe('function keys F1-F12', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('F1 → CSI P (SS3 form)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F1' }), flags);\n        assert.strictEqual(result.key, '\\x1bOP');\n      });\n\n      it('F2 → CSI Q (SS3 form)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F2' }), flags);\n        assert.strictEqual(result.key, '\\x1bOQ');\n      });\n\n      it('F3 → CSI R (SS3 form)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F3' }), flags);\n        assert.strictEqual(result.key, '\\x1bOR');\n      });\n\n      it('F4 → CSI S (SS3 form)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F4' }), flags);\n        assert.strictEqual(result.key, '\\x1bOS');\n      });\n\n      it('F5 → CSI 15 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F5' }), flags);\n        assert.strictEqual(result.key, '\\x1b[15~');\n      });\n\n      it('F6 → CSI 17 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F6' }), flags);\n        assert.strictEqual(result.key, '\\x1b[17~');\n      });\n\n      it('F7 → CSI 18 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F7' }), flags);\n        assert.strictEqual(result.key, '\\x1b[18~');\n      });\n\n      it('F8 → CSI 19 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F8' }), flags);\n        assert.strictEqual(result.key, '\\x1b[19~');\n      });\n\n      it('F9 → CSI 20 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F9' }), flags);\n        assert.strictEqual(result.key, '\\x1b[20~');\n      });\n\n      it('F10 → CSI 21 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F10' }), flags);\n        assert.strictEqual(result.key, '\\x1b[21~');\n      });\n\n      it('F11 → CSI 23 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F11' }), flags);\n        assert.strictEqual(result.key, '\\x1b[23~');\n      });\n\n      it('F12 → CSI 24 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F12' }), flags);\n        assert.strictEqual(result.key, '\\x1b[24~');\n      });\n\n      it('Shift+F1 → CSI 1;2 P', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F1', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[1;2P');\n      });\n\n      it('Ctrl+F5 → CSI 15;5 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F5', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[15;5~');\n      });\n    });\n\n    describe('extended function keys F13-F35 (Private Use Area)', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('F13 → CSI 57376 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F13' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57376u');\n      });\n\n      it('F14 → CSI 57377 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F14' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57377u');\n      });\n\n      it('F20 → CSI 57383 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F20' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57383u');\n      });\n\n      it('F24 → CSI 57387 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'F24' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57387u');\n      });\n    });\n\n    describe('numpad keys (Private Use Area)', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('Numpad0 → CSI 57399 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '0', code: 'Numpad0' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57399u');\n      });\n\n      it('Numpad1 → CSI 57400 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '1', code: 'Numpad1' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57400u');\n      });\n\n      it('Numpad9 → CSI 57408 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '9', code: 'Numpad9' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57408u');\n      });\n\n      it('NumpadDecimal → CSI 57409 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '.', code: 'NumpadDecimal' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57409u');\n      });\n\n      it('NumpadDivide → CSI 57410 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '/', code: 'NumpadDivide' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57410u');\n      });\n\n      it('NumpadMultiply → CSI 57411 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '*', code: 'NumpadMultiply' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57411u');\n      });\n\n      it('NumpadSubtract → CSI 57412 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '-', code: 'NumpadSubtract' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57412u');\n      });\n\n      it('NumpadAdd → CSI 57413 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '+', code: 'NumpadAdd' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57413u');\n      });\n\n      it('NumpadEnter → CSI 57414 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Enter', code: 'NumpadEnter' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57414u');\n      });\n\n      it('NumpadEqual → CSI 57415 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '=', code: 'NumpadEqual' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57415u');\n      });\n\n      it('Ctrl+Numpad5 → CSI 57404;5 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '5', code: 'Numpad5', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57404;5u');\n      });\n    });\n\n    describe('modifier keys (Private Use Area)', () => {\n      const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES;\n\n      it('Left Shift → CSI 57441 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57441;2u');\n      });\n\n      it('Right Shift → CSI 57447 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftRight', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57447;2u');\n      });\n\n      it('Left Control → CSI 57442 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Control', code: 'ControlLeft', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57442;5u');\n      });\n\n      it('Right Control → CSI 57448 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Control', code: 'ControlRight', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57448;5u');\n      });\n\n      it('Left Alt → CSI 57443 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Alt', code: 'AltLeft', altKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57443;3u');\n      });\n\n      it('Right Alt → CSI 57449 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Alt', code: 'AltRight', altKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57449;3u');\n      });\n\n      it('Left Meta/Super → CSI 57444 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Meta', code: 'MetaLeft', metaKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57444;9u');\n      });\n\n      it('Right Meta/Super → CSI 57450 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Meta', code: 'MetaRight', metaKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[57450;9u');\n      });\n\n      it('CapsLock → CSI 57358 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'CapsLock', code: 'CapsLock' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57358u');\n      });\n\n      it('NumLock → CSI 57360 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'NumLock', code: 'NumLock' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57360u');\n      });\n\n      it('ScrollLock → CSI 57359 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ScrollLock', code: 'ScrollLock' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57359u');\n      });\n    });\n\n    describe('event types (press/repeat/release)', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES | KittyKeyboardFlags.REPORT_EVENT_TYPES;\n\n      it('press event (default, no suffix)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.PRESS);\n        assert.strictEqual(result.key, '\\x1b[97u');\n      });\n\n      it('press event explicit :1 when modifiers present', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.PRESS);\n        assert.strictEqual(result.key, '\\x1b[97;5u');\n      });\n\n      it('repeat event → :2 suffix', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.REPEAT);\n        assert.strictEqual(result.key, '\\x1b[97;1:2u');\n      });\n\n      it('release event → :3 suffix', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, '\\x1b[97;1:3u');\n      });\n\n      it('release with modifier → mod:3', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, '\\x1b[97;5:3u');\n      });\n\n      it('repeat with modifier → mod:2', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', shiftKey: true, altKey: true }), flags, KittyKeyboardEventType.REPEAT);\n        assert.strictEqual(result.key, '\\x1b[97;4:2u');\n      });\n\n      it('functional key release → CSI code;1:3 ~', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Delete' }), flags, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, '\\x1b[3;1:3~');\n      });\n\n      it('modifier key release includes its own bit cleared', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: false }), flags | KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, '\\x1b[57441;1:3u');\n      });\n    });\n\n    describe('modifier-only reporting', () => {\n      const flags = KittyKeyboardFlags.REPORT_EVENT_TYPES;\n\n      it('does not report modifier press without REPORT_ALL_KEYS_AS_ESCAPE_CODES', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: true }), flags);\n        assert.strictEqual(result.key, undefined);\n      });\n\n      it('does not report modifier release without REPORT_ALL_KEYS_AS_ESCAPE_CODES', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: false }), flags, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, undefined);\n      });\n    });\n\n    describe('REPORT_ALL_KEYS_AS_ESCAPE_CODES flag', () => {\n      const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES;\n\n      it('lowercase letter → CSI codepoint u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flags);\n        assert.strictEqual(result.key, '\\x1b[97u');\n      });\n\n      it('uppercase letter uses lowercase codepoint', () => {\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;2u');\n      });\n\n      it('digit → CSI codepoint u', () => {\n        const result = kitty.evaluate(createEvent({ key: '5' }), flags);\n        assert.strictEqual(result.key, '\\x1b[53u');\n      });\n\n      it('punctuation → CSI codepoint u', () => {\n        assert.strictEqual(kitty.evaluate(createEvent({ key: '.' }), flags).key, '\\x1b[46u');\n        assert.strictEqual(kitty.evaluate(createEvent({ key: ',' }), flags).key, '\\x1b[44u');\n        assert.strictEqual(kitty.evaluate(createEvent({ key: ';' }), flags).key, '\\x1b[59u');\n        assert.strictEqual(kitty.evaluate(createEvent({ key: '/' }), flags).key, '\\x1b[47u');\n      });\n\n      it('brackets → CSI codepoint u', () => {\n        assert.strictEqual(kitty.evaluate(createEvent({ key: '[' }), flags).key, '\\x1b[91u');\n        assert.strictEqual(kitty.evaluate(createEvent({ key: ']' }), flags).key, '\\x1b[93u');\n      });\n\n      it('space → CSI 32 u', () => {\n        const result = kitty.evaluate(createEvent({ key: ' ' }), flags);\n        assert.strictEqual(result.key, '\\x1b[32u');\n      });\n    });\n\n    describe('REPORT_ASSOCIATED_TEXT flag', () => {\n      const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT;\n\n      it('regular key includes text codepoint', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;;97u');\n      });\n\n      it('shifted key includes shifted text', () => {\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;2;65u');\n      });\n\n      it('Ctrl+key omits text (control code)', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;5u');\n      });\n\n      it('functional key has no text', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags);\n        assert.strictEqual(result.key, '\\x1b[27u');\n      });\n\n      it('release event has no text', () => {\n        const flagsWithEvents = flags | KittyKeyboardFlags.REPORT_EVENT_TYPES;\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flagsWithEvents, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, '\\x1b[97;1:3u');\n      });\n\n      it('digit with text', () => {\n        const result = kitty.evaluate(createEvent({ key: '5' }), flags);\n        assert.strictEqual(result.key, '\\x1b[53;;53u');\n      });\n\n      it('Shift+digit shows shifted symbol', () => {\n        const result = kitty.evaluate(createEvent({ key: '%', shiftKey: true, code: 'Digit5' }), flags);\n        assert.strictEqual(result.key, '\\x1b[53;2;37u');\n      });\n    });\n\n    describe('REPORT_ALTERNATE_KEYS flag', () => {\n      const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ALTERNATE_KEYS;\n\n      it('Shift+a includes shifted key → CSI 97:65 ; 2 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flags);\n        assert.strictEqual(result.key, '\\x1b[97:65;2u');\n      });\n\n      it('unshifted key has no alternate', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a', code: 'KeyA' }), flags);\n        assert.strictEqual(result.key, '\\x1b[97u');\n      });\n\n      it('Shift+5 includes shifted key → CSI 53:37 ; 2 u', () => {\n        const result = kitty.evaluate(createEvent({ key: '%', shiftKey: true, code: 'Digit5' }), flags);\n        assert.strictEqual(result.key, '\\x1b[53:37;2u');\n      });\n\n      it('functional keys have no shifted alternate', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Escape', shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[27;2u');\n      });\n    });\n\n    describe('REPORT_ALTERNATE_KEYS with REPORT_ASSOCIATED_TEXT', () => {\n      const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ALTERNATE_KEYS | KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT;\n\n      it('Shift+a → CSI 97:65 ; 2 ; 65 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flags);\n        assert.strictEqual(result.key, '\\x1b[97:65;2;65u');\n      });\n\n      it('Shift+a release → CSI 97:65 ; 2:3 u (no text)', () => {\n        const flagsWithEvents = flags | KittyKeyboardFlags.REPORT_EVENT_TYPES;\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flagsWithEvents, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, '\\x1b[97:65;2:3u');\n      });\n    });\n\n    describe('release events without REPORT_EVENT_TYPES', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('should not generate key sequence for release events', () => {\n        const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.RELEASE);\n        assert.strictEqual(result.key, undefined);\n      });\n    });\n\n    describe('edge cases', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('shift+letter sends plain character in DISAMBIGUATE mode', () => {\n        // Shift+A produces printable \"A\", not ambiguous, so send plain character\n        const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);\n        assert.strictEqual(result.key, 'A');\n      });\n\n      it('ctrl+shift+a sends lowercase codepoint 97', () => {\n        const result = kitty.evaluate(createEvent({ key: 'A', ctrlKey: true, shiftKey: true }), flags);\n        assert.strictEqual(result.key, '\\x1b[97;6u');\n      });\n\n      it('Dead key produces no output', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Dead' }), flags);\n        assert.strictEqual(result.key, undefined);\n      });\n\n      it('Unidentified key produces no output', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Unidentified' }), flags);\n        assert.strictEqual(result.key, undefined);\n      });\n\n      it('PrintScreen → CSI 57361 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'PrintScreen' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57361u');\n      });\n\n      it('Pause → CSI 57362 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'Pause' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57362u');\n      });\n\n      it('ContextMenu → CSI 57363 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'ContextMenu' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57363u');\n      });\n    });\n\n    describe('media keys (Private Use Area)', () => {\n      const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;\n\n      it('MediaPlayPause → CSI 57430 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'MediaPlayPause' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57430u');\n      });\n\n      it('MediaStop → CSI 57432 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'MediaStop' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57432u');\n      });\n\n      it('MediaTrackNext → CSI 57435 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'MediaTrackNext' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57435u');\n      });\n\n      it('MediaTrackPrevious → CSI 57436 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'MediaTrackPrevious' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57436u');\n      });\n\n      it('AudioVolumeDown → CSI 57438 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'AudioVolumeDown' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57438u');\n      });\n\n      it('AudioVolumeUp → CSI 57439 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'AudioVolumeUp' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57439u');\n      });\n\n      it('AudioVolumeMute → CSI 57440 u', () => {\n        const result = kitty.evaluate(createEvent({ key: 'AudioVolumeMute' }), flags);\n        assert.strictEqual(result.key, '\\x1b[57440u');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/input/KittyKeyboard.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Kitty keyboard protocol implementation.\n * @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n */\n\nimport { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types';\nimport { C0 } from 'common/data/EscapeSequences';\n\n/**\n * Kitty keyboard protocol enhancement flags (bitfield).\n */\nexport const enum KittyKeyboardFlags {\n  NONE                            = 0b00000,\n  /** Disambiguate escape codes - fixes ambiguous legacy encodings */\n  DISAMBIGUATE_ESCAPE_CODES       = 0b00001,\n  /** Report event types - press/repeat/release */\n  REPORT_EVENT_TYPES              = 0b00010,\n  /** Report alternate keys - shifted key and base layout key */\n  REPORT_ALTERNATE_KEYS           = 0b00100,\n  /** Report all keys as escape codes - text-producing keys as CSI u */\n  REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b01000,\n  /** Report associated text - includes text codepoints in escape code */\n  REPORT_ASSOCIATED_TEXT          = 0b10000,\n}\n\n/**\n * Kitty keyboard event types.\n */\nexport const enum KittyKeyboardEventType {\n  PRESS = 1,\n  REPEAT = 2,\n  RELEASE = 3,\n}\n\n/**\n * Kitty modifier bits (different from xterm modifier encoding).\n * Value sent = 1 + modifier_bits\n */\nexport const enum KittyKeyboardModifiers {\n  SHIFT     = 0b00000001,\n  ALT       = 0b00000010,\n  CTRL      = 0b00000100,\n  SUPER     = 0b00001000,\n  HYPER     = 0b00010000,\n  META      = 0b00100000,\n  CAPS_LOCK = 0b01000000,\n  NUM_LOCK  = 0b10000000,\n}\n\n/**\n * Kitty keyboard protocol handler class.\n * Encapsulates all key code mappings and encoding logic.\n */\nexport class KittyKeyboard {\n  /**\n   * Functional key codes for Kitty protocol.\n   * Keys that don't produce text have specific unicode codepoint mappings.\n   */\n  private readonly _functionalKeyCodes: { [key: string]: number } = {\n    'Escape': 27,\n    'Enter': 13,\n    'Tab': 9,\n    'Backspace': 127,\n    'CapsLock': 57358,\n    'ScrollLock': 57359,\n    'NumLock': 57360,\n    'PrintScreen': 57361,\n    'Pause': 57362,\n    'ContextMenu': 57363,\n    // F13-F35 (F1-F12 use legacy encoding)\n    'F13': 57376,\n    'F14': 57377,\n    'F15': 57378,\n    'F16': 57379,\n    'F17': 57380,\n    'F18': 57381,\n    'F19': 57382,\n    'F20': 57383,\n    'F21': 57384,\n    'F22': 57385,\n    'F23': 57386,\n    'F24': 57387,\n    'F25': 57388,\n    // Keypad keys\n    'KP_0': 57399,\n    'KP_1': 57400,\n    'KP_2': 57401,\n    'KP_3': 57402,\n    'KP_4': 57403,\n    'KP_5': 57404,\n    'KP_6': 57405,\n    'KP_7': 57406,\n    'KP_8': 57407,\n    'KP_9': 57408,\n    'KP_Decimal': 57409,\n    'KP_Divide': 57410,\n    'KP_Multiply': 57411,\n    'KP_Subtract': 57412,\n    'KP_Add': 57413,\n    'KP_Enter': 57414,\n    'KP_Equal': 57415,\n    // Modifier keys\n    'ShiftLeft': 57441,\n    'ShiftRight': 57447,\n    'ControlLeft': 57442,\n    'ControlRight': 57448,\n    'AltLeft': 57443,\n    'AltRight': 57449,\n    'MetaLeft': 57444,\n    'MetaRight': 57450,\n    // Media keys\n    'MediaPlayPause': 57430,\n    'MediaStop': 57432,\n    'MediaTrackNext': 57435,\n    'MediaTrackPrevious': 57436,\n    'AudioVolumeDown': 57438,\n    'AudioVolumeUp': 57439,\n    'AudioVolumeMute': 57440\n  };\n\n  /**\n   * Keys that use CSI ~ encoding with a number parameter.\n   */\n  private readonly _csiTildeKeys: { [key: string]: number } = {\n    'Insert': 2,\n    'Delete': 3,\n    'PageUp': 5,\n    'PageDown': 6,\n    'F5': 15,\n    'F6': 17,\n    'F7': 18,\n    'F8': 19,\n    'F9': 20,\n    'F10': 21,\n    'F11': 23,\n    'F12': 24\n  };\n\n  /**\n   * Keys that use CSI letter encoding (arrows, Home, End).\n   */\n  private readonly _csiLetterKeys: { [key: string]: string } = {\n    'ArrowUp': 'A',\n    'ArrowDown': 'B',\n    'ArrowRight': 'C',\n    'ArrowLeft': 'D',\n    'Home': 'H',\n    'End': 'F'\n  };\n\n  /**\n   * Function keys F1-F4 use SS3 encoding without modifiers.\n   */\n  private readonly _ss3FunctionKeys: { [key: string]: string } = {\n    'F1': 'P',\n    'F2': 'Q',\n    'F3': 'R',\n    'F4': 'S'\n  };\n\n  /**\n   * Map browser key codes to Kitty numpad codes.\n   */\n  private _getNumpadKeyCode(ev: IKeyboardEvent): number | undefined {\n    if (ev.code.startsWith('Numpad')) {\n      const suffix = ev.code.slice(6);\n      if (suffix >= '0' && suffix <= '9') {\n        return 57399 + parseInt(suffix, 10);\n      }\n      switch (suffix) {\n        case 'Decimal': return 57409;\n        case 'Divide': return 57410;\n        case 'Multiply': return 57411;\n        case 'Subtract': return 57412;\n        case 'Add': return 57413;\n        case 'Enter': return 57414;\n        case 'Equal': return 57415;\n      }\n    }\n    return undefined;\n  }\n\n  /**\n   * Get modifier key code from code property.\n   */\n  private _getModifierKeyCode(ev: IKeyboardEvent): number | undefined {\n    switch (ev.code) {\n      case 'ShiftLeft': return 57441;\n      case 'ShiftRight': return 57447;\n      case 'ControlLeft': return 57442;\n      case 'ControlRight': return 57448;\n      case 'AltLeft': return 57443;\n      case 'AltRight': return 57449;\n      case 'MetaLeft': return 57444;\n      case 'MetaRight': return 57450;\n    }\n    return undefined;\n  }\n\n  /**\n   * Encode modifiers for Kitty protocol.\n   * Returns 1 + modifier bits, or 0 if no modifiers.\n   */\n  private _encodeModifiers(ev: IKeyboardEvent): number {\n    let mods = 0;\n    if (ev.shiftKey) mods |= KittyKeyboardModifiers.SHIFT;\n    if (ev.altKey) mods |= KittyKeyboardModifiers.ALT;\n    if (ev.ctrlKey) mods |= KittyKeyboardModifiers.CTRL;\n    if (ev.metaKey) mods |= KittyKeyboardModifiers.SUPER;\n    return mods > 0 ? mods + 1 : 0;\n  }\n\n  /**\n   * Get the unicode key code for a keyboard event.\n   * Returns the lowercase codepoint for letters.\n   * For shifted keys, uses the code property to get the base key.\n   */\n  private _getKeyCode(ev: IKeyboardEvent): number | undefined {\n    const numpadCode = this._getNumpadKeyCode(ev);\n    if (numpadCode !== undefined) {\n      return numpadCode;\n    }\n\n    const modifierCode = this._getModifierKeyCode(ev);\n    if (modifierCode !== undefined) {\n      return modifierCode;\n    }\n\n    const funcCode = this._functionalKeyCodes[ev.key];\n    if (funcCode !== undefined) {\n      return funcCode;\n    }\n\n    if (ev.shiftKey && ev.code) {\n      if (ev.code.startsWith('Digit') && ev.code.length === 6) {\n        const digit = ev.code.charAt(5);\n        if (digit >= '0' && digit <= '9') {\n          return digit.charCodeAt(0);\n        }\n      }\n      if (ev.code.startsWith('Key') && ev.code.length === 4) {\n        const letter = ev.code.charAt(3).toLowerCase();\n        return letter.charCodeAt(0);\n      }\n    }\n\n    if (ev.key.length === 1) {\n      const code = ev.key.codePointAt(0)!;\n      if (code >= 65 && code <= 90) {\n        return code + 32;\n      }\n      return code;\n    }\n\n    return undefined;\n  }\n\n  /**\n   * Check if a key is a modifier key.\n   */\n  private _isModifierKey(ev: IKeyboardEvent): boolean {\n    return ev.key === 'Shift' || ev.key === 'Control' || ev.key === 'Alt' || ev.key === 'Meta';\n  }\n\n  /**\n   * Build CSI letter sequence for arrow keys, Home, End.\n   * Format: CSI [1;mod] letter\n   */\n  private _buildCsiLetterSequence(\n    letter: string,\n    modifiers: number,\n    eventType: KittyKeyboardEventType,\n    reportEventTypes: boolean\n  ): string {\n    const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;\n\n    if (modifiers > 0 || needsEventType) {\n      let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');\n      if (needsEventType) {\n        seq += ':' + eventType;\n      }\n      seq += letter;\n      return seq;\n    }\n    return C0.ESC + '[' + letter;\n  }\n\n  /**\n   * Build SS3 sequence for F1-F4.\n   * Without modifiers: SS3 letter\n   * With modifiers: CSI 1;mod letter\n   */\n  private _buildSs3Sequence(\n    letter: string,\n    modifiers: number,\n    eventType: KittyKeyboardEventType,\n    reportEventTypes: boolean\n  ): string {\n    const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;\n\n    if (modifiers > 0 || needsEventType) {\n      let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');\n      if (needsEventType) {\n        seq += ':' + eventType;\n      }\n      seq += letter;\n      return seq;\n    }\n    return C0.ESC + 'O' + letter;\n  }\n\n  /**\n   * Build CSI ~ sequence for Insert, Delete, PageUp/Down, F5-F12.\n   * Format: CSI number [;mod[:event]] ~\n   */\n  private _buildCsiTildeSequence(\n    number: number,\n    modifiers: number,\n    eventType: KittyKeyboardEventType,\n    reportEventTypes: boolean\n  ): string {\n    const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;\n\n    let seq = C0.ESC + '[' + number;\n    if (modifiers > 0 || needsEventType) {\n      seq += ';' + (modifiers > 0 ? modifiers : '1');\n      if (needsEventType) {\n        seq += ':' + eventType;\n      }\n    }\n    seq += '~';\n    return seq;\n  }\n\n  /**\n   * Build CSI u sequence.\n   * Format: CSI keycode[:shifted[:base]] [;mod[:event][;text]] u\n   */\n  private _buildCsiUSequence(\n    ev: IKeyboardEvent,\n    keyCode: number,\n    modifiers: number,\n    eventType: KittyKeyboardEventType,\n    flags: number,\n    isFunc: boolean,\n    isMod: boolean\n  ): string {\n    const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);\n    const reportAlternateKeys = !!(flags & KittyKeyboardFlags.REPORT_ALTERNATE_KEYS);\n\n    let seq = C0.ESC + '[' + keyCode;\n\n    let shiftedKey: number | undefined;\n    if (reportAlternateKeys && ev.shiftKey && ev.key.length === 1 && !isFunc && !isMod) {\n      shiftedKey = ev.key.codePointAt(0);\n      seq += ':' + shiftedKey;\n    }\n\n    const reportAssociatedText = !!(flags & KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT) &&\n      eventType !== KittyKeyboardEventType.RELEASE &&\n      ev.key.length === 1 &&\n      !isFunc &&\n      !isMod &&\n      !ev.ctrlKey;\n    const textCode = reportAssociatedText ? ev.key.codePointAt(0) : undefined;\n\n    const needsEventType = reportEventTypes &&\n      eventType !== KittyKeyboardEventType.PRESS &&\n      (eventType === KittyKeyboardEventType.RELEASE || textCode === undefined);\n\n    if (modifiers > 0 || needsEventType || textCode !== undefined) {\n      seq += ';';\n      if (modifiers > 0) {\n        seq += modifiers;\n      } else if (needsEventType) {\n        seq += '1';\n      }\n      if (needsEventType) {\n        seq += ':' + eventType;\n      }\n    }\n\n    if (textCode !== undefined) {\n      seq += ';' + textCode;\n    }\n\n    seq += 'u';\n    return seq;\n  }\n\n  /**\n   * Evaluate a keyboard event using Kitty keyboard protocol.\n   *\n   * @param ev The keyboard event.\n   * @param flags The active Kitty keyboard enhancement flags.\n   * @param eventType The event type (press, repeat, release).\n   * @returns The keyboard result with the encoded key sequence.\n   */\n  public evaluate(\n    ev: IKeyboardEvent,\n    flags: number,\n    eventType: KittyKeyboardEventType = KittyKeyboardEventType.PRESS\n  ): IKeyboardResult {\n    const result: IKeyboardResult = {\n      type: KeyboardResultType.SEND_KEY,\n      cancel: false,\n      key: undefined\n    };\n\n    const modifiers = this._encodeModifiers(ev);\n    const isMod = this._isModifierKey(ev);\n    const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);\n\n    if (!reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) {\n      return result;\n    }\n\n    if (isMod && !(flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES)) {\n      return result;\n    }\n\n    const csiLetter = this._csiLetterKeys[ev.key];\n    if (csiLetter) {\n      result.key = this._buildCsiLetterSequence(csiLetter, modifiers, eventType, reportEventTypes);\n      result.cancel = true;\n      return result;\n    }\n\n    const ss3Letter = this._ss3FunctionKeys[ev.key];\n    if (ss3Letter) {\n      result.key = this._buildSs3Sequence(ss3Letter, modifiers, eventType, reportEventTypes);\n      result.cancel = true;\n      return result;\n    }\n\n    const tildeCode = this._csiTildeKeys[ev.key];\n    if (tildeCode !== undefined) {\n      result.key = this._buildCsiTildeSequence(tildeCode, modifiers, eventType, reportEventTypes);\n      result.cancel = true;\n      return result;\n    }\n\n    const keyCode = this._getKeyCode(ev);\n    if (keyCode === undefined) {\n      return result;\n    }\n\n    const isFunc = this._functionalKeyCodes[ev.key] !== undefined || this._getNumpadKeyCode(ev) !== undefined;\n\n    let useCsiU = false;\n\n    if (flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) {\n      useCsiU = true;\n    } else if (reportEventTypes) {\n      useCsiU = true;\n    } else if (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES) {\n      // Per spec, Enter/Tab/Backspace \"still generate the same bytes as in legacy\n      // mode\" and consider space to be a text-generating key, so these skip the isFunc fast-path\n      // and only get CSI u when modifiers are present (handled below).\n      const isDisambiguateLegacy = keyCode === 13 || keyCode === 9 || keyCode === 127;\n      if (isFunc && !isDisambiguateLegacy) {\n        useCsiU = true;\n      } else if (modifiers > 0) {\n        if (ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.key.length === 1) {\n          useCsiU = false;\n        } else {\n          useCsiU = true;\n        }\n      }\n    }\n\n    if (useCsiU) {\n      result.key = this._buildCsiUSequence(ev, keyCode, modifiers, eventType, flags, isFunc, isMod);\n      result.cancel = true;\n    } else {\n      const legacyByte = keyCode === 13 ? '\\r' : keyCode === 9 ? '\\t' : keyCode === 127 ? '\\x7f' : undefined;\n      if (legacyByte) {\n        result.key = legacyByte;\n      } else if (ev.key.length === 1 && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {\n        result.key = ev.key;\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Check if Kitty protocol should be used based on flags.\n   */\n  public static shouldUseProtocol(flags: number): boolean {\n    return flags > 0;\n  }\n}\n"
  },
  {
    "path": "src/common/input/TextDecoder.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder';\nimport { encode } from 'utf8';\n\n\n// convert UTF32 codepoints to string\nfunction toString(data: Uint32Array, length: number): string {\n  if ((String as any).fromCodePoint) {\n    return (String as any).fromCodePoint.apply(null, data.subarray(0, length));\n  }\n  let result = '';\n  for (let i = 0; i < length; ++i) {\n    result += stringFromCodePoint(data[i]);\n  }\n  return result;\n}\n\n// convert \"bytestring\" (charCode 0-255) to bytes\nfunction fromByteString(s: string): Uint8Array {\n  const result = new Uint8Array(s.length);\n  for (let i = 0; i < s.length; ++i) {\n    result[i] = s.charCodeAt(i);\n  }\n  return result;\n}\n\nfunction assertDecodedRange(\n  min: number,\n  max: number,\n  skip: (codePoint: number) => boolean,\n  buildChar: (codePoint: number) => string,\n  decode: (input: string, target: Uint32Array) => number,\n  outputToString: (data: Uint32Array, length: number) => string\n): void {\n  if (max <= min) {\n    return;\n  }\n  let input = '';\n  let count = 0;\n  for (let i = min; i < max; ++i) {\n    if (skip(i)) {\n      continue;\n    }\n    input += buildChar(i);\n    count++;\n  }\n  const target = new Uint32Array(count);\n  const length = decode(input, target);\n  assert.equal(length, count);\n  let mismatchIndex = -1;\n  let index = 0;\n  for (let i = min; i < max; ++i) {\n    if (skip(i)) {\n      continue;\n    }\n    if (target[index] !== i) {\n      mismatchIndex = index;\n      break;\n    }\n    index++;\n  }\n  assert.equal(mismatchIndex, -1);\n  assert.equal(outputToString(target, length), input);\n}\n\nconst BATCH_SIZE = 8192;\n\nconst TEST_STRINGS = [\n  'Лорем ипсум долор сит амет, ех сеа аццусам диссентиет. Ан еос стет еирмод витуперата. Иус дицерет урбанитас ет. Ан при алтера долорес сплендиде, цу яуо интегре денияуе, игнота волуптариа инструцтиор цу вим.',\n  'ლორემ იფსუმ დოლორ სით ამეთ, ფაცერ მუციუს ცონსეთეთურ ყუო იდ, ფერ ვივენდუმ ყუაერენდუმ ეა, ესთ ამეთ მოვეთ სუავითათე ცუ. ვითაე სენსიბუს ან ვიხ. ეხერცი დეთერრუისსეთ უთ ყუი. ვოცენთ დებითის ადიფისცი ეთ ფერ. ნეც ან ფეუგაით ფორენსიბუს ინთერესსეთ. იდ დიცო რიდენს იუს. დისსენთიეთ ცონსეყუუნთურ სედ ნე, ნოვუმ მუნერე ეუმ ათ, ნე ეუმ ნიჰილ ირაცუნდია ურბანითას.',\n  'अधिकांश अमितकुमार प्रोत्साहित मुख्य जाने प्रसारन विश्लेषण विश्व दारी अनुवादक अधिकांश नवंबर विषय गटकउसि गोपनीयता विकास जनित परस्पर गटकउसि अन्तरराष्ट्रीयकरन होसके मानव पुर्णता कम्प्युटर यन्त्रालय प्रति साधन',\n  '覧六子当聞社計文護行情投身斗来。増落世的況上席備界先関権能万。本物挙歯乳全事携供板栃果以。頭月患端撤競見界記引去法条公泊候。決海備駆取品目芸方用朝示上用報。講申務紙約週堂出応理田流団幸稿。起保帯吉対阜庭支肯豪彰属本躍。量抑熊事府募動極都掲仮読岸。自続工就断庫指北速配鳴約事新住米信中験。婚浜袋著金市生交保他取情距。',\n  '八メル務問へふらく博辞説いわょ読全タヨムケ東校どっ知壁テケ禁去フミ人過を装5階がねぜ法逆はじ端40落ミ予竹マヘナセ任1悪た。省ぜりせ製暇ょへそけ風井イ劣手はぼまず郵富法く作断タオイ取座ゅょが出作ホシ月給26島ツチ皇面ユトクイ暮犯リワナヤ断連こうでつ蔭柔薄とレにの。演めけふぱ損田転10得観びトげぎ王物鉄夜がまけ理惜くち牡提づ車惑参ヘカユモ長臓超漫ぼドかわ。',\n  '모든 국민은 행위시의 법률에 의하여 범죄를 구성하지 아니하는 행위로 소추되지 아니하며. 전직대통령의 신분과 예우에 관하여는 법률로 정한다, 국회는 헌법 또는 법률에 특별한 규정이 없는 한 재적의원 과반수의 출석과 출석의원 과반수의 찬성으로 의결한다. 군인·군무원·경찰공무원 기타 법률이 정하는 자가 전투·훈련등 직무집행과 관련하여 받은 손해에 대하여는 법률이 정하는 보상외에 국가 또는 공공단체에 공무원의 직무상 불법행위로 인한 배상은 청구할 수 없다.',\n  'كان فشكّل الشرقي مع, واحدة للمجهود تزامناً بعض بل. وتم جنوب للصين غينيا لم, ان وبدون وكسبت الأمور ذلك, أسر الخاسر الانجليزية هو. نفس لغزو مواقعها هو. الجو علاقة الصعداء انه أي, كما مع بمباركة للإتحاد الوزراء. ترتيب الأولى أن حدى, الشتوية باستحداث مدن بل, كان قد أوسع عملية. الأوضاع بالمطالبة كل قام, دون إذ شمال الربيع،. هُزم الخاصّة ٣٠ أما, مايو الصينية مع قبل.',\n  'או סדר החול מיזמי קרימינולוגיה. קהילה בגרסה לויקיפדים אל היא, של צעד ציור ואלקטרוניקה. מדע מה ברית המזנון ארכיאולוגיה, אל טבלאות מבוקשים כלל. מאמרשיחהצפה העריכהגירסאות שכל אל, כתב עיצוב מושגי של. קבלו קלאסיים ב מתן. נבחרים אווירונאוטיקה אם מלא, לוח למנוע ארכיאולוגיה מה. ארץ לערוך בקרבת מונחונים או, עזרה רקטות לויקיפדים אחר גם.',\n  'Лорем ლორემ अधिकांश 覧六子 八メル 모든 בקרבת 💮 😂 äggg 123€ 𝄞.'\n];\n\ndescribe('text encodings', () => {\n  it('stringFromCodePoint/utf32ToString', () => {\n    const s = 'abcdefg';\n    const data = new Uint32Array(s.length);\n    for (let i = 0; i < s.length; ++i) {\n      data[i] = s.charCodeAt(i);\n      assert.equal(stringFromCodePoint(data[i]), s[i]);\n    }\n    assert.equal(utf32ToString(data), s);\n  });\n\n  describe('StringToUtf32 decoder', () => {\n    describe('full codepoint test', () => {\n      for (let min = 0; min < 65535; min += BATCH_SIZE) {\n        const max = Math.min(min + BATCH_SIZE, 65536);\n        it(`${formatRange(min, max)}`, () => {\n          const decoder = new StringToUtf32();\n          assertDecodedRange(\n            min,\n            max,\n            (i) => (i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF,\n            (i) => String.fromCharCode(i),\n            (input, target) => decoder.decode(input, target),\n            (data, length) => utf32ToString(data, 0, length)\n          );\n        });\n      }\n      for (let min = 65536; min < 0x10FFFF; min += BATCH_SIZE) {\n        const max = Math.min(min + BATCH_SIZE, 0x10FFFF);\n        it(`${formatRange(min, max)} (surrogates)`, () => {\n          const decoder = new StringToUtf32();\n          assertDecodedRange(\n            min,\n            max,\n            () => false,\n            (i) => {\n              const codePoint = i - 0x10000;\n              return String.fromCharCode((codePoint >> 10) + 0xD800, (codePoint % 0x400) + 0xDC00);\n            },\n            (input, target) => decoder.decode(input, target),\n            (data, length) => utf32ToString(data, 0, length)\n          );\n        });\n      }\n\n      it('0xFEFF(BOM)', () => {\n        const decoder = new StringToUtf32();\n        const target = new Uint32Array(5);\n        const length = decoder.decode(String.fromCharCode(0xFEFF), target);\n        assert.equal(length, 0);\n        decoder.clear();\n      });\n    });\n\n    it('test strings', () => {\n      const decoder = new StringToUtf32();\n      const target = new Uint32Array(500);\n      for (let i = 0; i < TEST_STRINGS.length; ++i) {\n        const length = decoder.decode(TEST_STRINGS[i], target);\n        assert.equal(toString(target, length), TEST_STRINGS[i]);\n        decoder.clear();\n      }\n    });\n\n    describe('stream handling', () => {\n      it('surrogates mixed advance by 1', () => {\n        const decoder = new StringToUtf32();\n        const target = new Uint32Array(5);\n        const input = 'Ä€𝄞Ö𝄞€Ü𝄞€';\n        let decoded = '';\n        for (let i = 0; i < input.length; ++i) {\n          const written = decoder.decode(input[i], target);\n          decoded += toString(target, written);\n        }\n        assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');\n      });\n    });\n  });\n\n  describe('Utf8ToUtf32 decoder', () => {\n    describe('full codepoint test', () => {\n      for (let min = 0; min < 65535; min += BATCH_SIZE) {\n        const max = Math.min(min + BATCH_SIZE, 65536);\n        it(`${formatRange(min, max)} (1/2/3 byte sequences)`, () => {\n          const decoder = new Utf8ToUtf32();\n          assertDecodedRange(\n            min,\n            max,\n            (i) => (i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF,\n            (i) => String.fromCharCode(i),\n            (input, target) => decoder.decode(fromByteString(encode(input)), target),\n            (data, length) => toString(data, length)\n          );\n        });\n      }\n      for (let minRaw = 60000; minRaw < 0x10FFFF; minRaw += BATCH_SIZE) {\n        const min = Math.max(minRaw, 65536);\n        const max = Math.min(minRaw + BATCH_SIZE, 0x10FFFF);\n        it(`${formatRange(min, max)} (4 byte sequences)`, function (): void {\n          const decoder = new Utf8ToUtf32();\n          assertDecodedRange(\n            min,\n            max,\n            () => false,\n            (i) => stringFromCodePoint(i),\n            (input, target) => decoder.decode(fromByteString(encode(input)), target),\n            (data, length) => toString(data, length)\n          );\n        });\n      }\n\n      it('0xFEFF(BOM)', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString(encode(String.fromCharCode(0xFEFF)));\n        const length = decoder.decode(utf8Data, target);\n        assert.equal(length, 0);\n        decoder.clear();\n      });\n    });\n\n    it('test strings', () => {\n      const decoder = new Utf8ToUtf32();\n      const target = new Uint32Array(500);\n      for (let i = 0; i < TEST_STRINGS.length; ++i) {\n        const utf8Data = fromByteString(encode(TEST_STRINGS[i]));\n        const length = decoder.decode(utf8Data, target);\n        assert.equal(toString(target, length), TEST_STRINGS[i]);\n        decoder.clear();\n      }\n    });\n\n    describe('stream handling', () => {\n      it('2 byte sequences - advance by 1', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xc3\\x84\\xc3\\x96\\xc3\\x9c\\xc3\\x9f\\xc3\\xb6\\xc3\\xa4\\xc3\\xbc');\n        let decoded = '';\n        for (let i = 0; i < utf8Data.length; ++i) {\n          const written = decoder.decode(utf8Data.slice(i, i + 1), target);\n          decoded += toString(target, written);\n        }\n        assert(decoded, 'ÄÖÜßöäü');\n      });\n\n      it('2/3 byte sequences - advance by 1', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xc3\\x84\\xe2\\x82\\xac\\xc3\\x96\\xe2\\x82\\xac\\xc3\\x9c\\xe2\\x82\\xac\\xc3\\x9f\\xe2\\x82\\xac\\xc3\\xb6\\xe2\\x82\\xac\\xc3\\xa4\\xe2\\x82\\xac\\xc3\\xbc');\n        let decoded = '';\n        for (let i = 0; i < utf8Data.length; ++i) {\n          const written = decoder.decode(utf8Data.slice(i, i + 1), target);\n          decoded += toString(target, written);\n        }\n        assert(decoded, 'Ä€Ö€Ü€ß€ö€ä€ü');\n      });\n\n      it('2/3/4 byte sequences - advance by 1', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xc3\\x84\\xe2\\x82\\xac\\xf0\\x9d\\x84\\x9e\\xc3\\x96\\xf0\\x9d\\x84\\x9e\\xe2\\x82\\xac\\xc3\\x9c\\xf0\\x9d\\x84\\x9e\\xe2\\x82\\xac');\n        let decoded = '';\n        for (let i = 0; i < utf8Data.length; ++i) {\n          const written = decoder.decode(utf8Data.slice(i, i + 1), target);\n          decoded += toString(target, written);\n        }\n        assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');\n      });\n\n      it('2/3/4 byte sequences - advance by 2', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xc3\\x84\\xe2\\x82\\xac\\xf0\\x9d\\x84\\x9e\\xc3\\x96\\xf0\\x9d\\x84\\x9e\\xe2\\x82\\xac\\xc3\\x9c\\xf0\\x9d\\x84\\x9e\\xe2\\x82\\xac');\n        let decoded = '';\n        for (let i = 0; i < utf8Data.length; i += 2) {\n          const written = decoder.decode(utf8Data.slice(i, i + 2), target);\n          decoded += toString(target, written);\n        }\n        assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');\n      });\n\n      it('2/3/4 byte sequences - advance by 3', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xc3\\x84\\xe2\\x82\\xac\\xf0\\x9d\\x84\\x9e\\xc3\\x96\\xf0\\x9d\\x84\\x9e\\xe2\\x82\\xac\\xc3\\x9c\\xf0\\x9d\\x84\\x9e\\xe2\\x82\\xac');\n        let decoded = '';\n        for (let i = 0; i < utf8Data.length; i += 3) {\n          const written = decoder.decode(utf8Data.slice(i, i + 3), target);\n          decoded += toString(target, written);\n        }\n        assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');\n      });\n\n      it('BOMs (3 byte sequences) - advance by 2', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xef\\xbb\\xbf\\xef\\xbb\\xbf');\n        let decoded = '';\n        for (let i = 0; i < utf8Data.length; i += 2) {\n          const written = decoder.decode(utf8Data.slice(i, i + 2), target);\n          decoded += toString(target, written);\n        }\n        assert.equal(decoded, '');\n      });\n\n      it('test break after 3 bytes - issue #2495', () => {\n        const decoder = new Utf8ToUtf32();\n        const target = new Uint32Array(5);\n        const utf8Data = fromByteString('\\xf0\\xa0\\x9c\\x8e');\n        let written = decoder.decode(utf8Data.slice(0, 3), target);\n        assert.equal(written, 0);\n        written = decoder.decode(utf8Data.slice(3), target);\n        assert.equal(written, 1);\n        assert(toString(target, written), '𠜎');\n      });\n    });\n  });\n});\n\nfunction formatRange(min: number, max: number): string {\n  return `${min}..${max} (0x${min.toString(16).toUpperCase()}..0x${max.toString(16).toUpperCase()})`;\n}\n"
  },
  {
    "path": "src/common/input/TextDecoder.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * Polyfill - Convert UTF32 codepoint into JS string.\n * Note: The built-in String.fromCodePoint happens to be much slower\n *       due to additional sanity checks. We can avoid them since\n *       we always operate on legal UTF32 (granted by the input decoders)\n *       and use this faster version instead.\n */\nexport function stringFromCodePoint(codePoint: number): string {\n  if (codePoint > 0xFFFF) {\n    codePoint -= 0x10000;\n    return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);\n  }\n  return String.fromCharCode(codePoint);\n}\n\n/**\n * Convert UTF32 char codes into JS string.\n * Basically the same as `stringFromCodePoint` but for multiple codepoints\n * in a loop (which is a lot faster).\n */\nexport function utf32ToString(data: Uint32Array, start: number = 0, end: number = data.length): string {\n  let result = '';\n  for (let i = start; i < end; ++i) {\n    let codepoint = data[i];\n    if (codepoint > 0xFFFF) {\n      // JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate\n      // pair conversion rules:\n      //  - subtract 0x10000 from code point, leaving a 20 bit number\n      //  - add high 10 bits to 0xD800  --> first surrogate\n      //  - add low 10 bits to 0xDC00   --> second surrogate\n      codepoint -= 0x10000;\n      result += String.fromCharCode((codepoint >> 10) + 0xD800) + String.fromCharCode((codepoint % 0x400) + 0xDC00);\n    } else {\n      result += String.fromCharCode(codepoint);\n    }\n  }\n  return result;\n}\n\n/**\n * StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints.\n * To keep the decoder in line with JS strings it handles single surrogates as UCS2.\n */\nexport class StringToUtf32 {\n  private _interim: number = 0;\n\n  /**\n   * Clears interim and resets decoder to clean state.\n   */\n  public clear(): void {\n    this._interim = 0;\n  }\n\n  /**\n   * Decode JS string to UTF32 codepoints.\n   * The methods assumes stream input and will store partly transmitted\n   * surrogate pairs and decode them with the next data chunk.\n   * Note: The method does no bound checks for target, therefore make sure\n   * the provided input data does not exceed the size of `target`.\n   * Returns the number of written codepoints in `target`.\n   */\n  public decode(input: string, target: Uint32Array): number {\n    const length = input.length;\n\n    if (!length) {\n      return 0;\n    }\n\n    let size = 0;\n    let startPos = 0;\n\n    // handle leftover surrogate high\n    if (this._interim) {\n      const second = input.charCodeAt(startPos++);\n      if (0xDC00 <= second && second <= 0xDFFF) {\n        target[size++] = (this._interim - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;\n      } else {\n        // illegal codepoint (USC2 handling)\n        target[size++] = this._interim;\n        target[size++] = second;\n      }\n      this._interim = 0;\n    }\n\n    for (let i = startPos; i < length; ++i) {\n      const code = input.charCodeAt(i);\n      // surrogate pair first\n      if (0xD800 <= code && code <= 0xDBFF) {\n        if (++i >= length) {\n          this._interim = code;\n          return size;\n        }\n        const second = input.charCodeAt(i);\n        if (0xDC00 <= second && second <= 0xDFFF) {\n          target[size++] = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;\n        } else {\n          // illegal codepoint (USC2 handling)\n          target[size++] = code;\n          target[size++] = second;\n        }\n        continue;\n      }\n      if (code === 0xFEFF) {\n        // BOM\n        continue;\n      }\n      target[size++] = code;\n    }\n    return size;\n  }\n}\n\n/**\n * Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints.\n */\nexport class Utf8ToUtf32 {\n  public interim: Uint8Array = new Uint8Array(3);\n\n  /**\n   * Clears interim bytes and resets decoder to clean state.\n   */\n  public clear(): void {\n    this.interim.fill(0);\n  }\n\n  /**\n   * Decodes UTF8 byte sequences in `input` to UTF32 codepoints in `target`.\n   * The methods assumes stream input and will store partly transmitted bytes\n   * and decode them with the next data chunk.\n   * Note: The method does no bound checks for target, therefore make sure\n   * the provided data chunk does not exceed the size of `target`.\n   * Returns the number of written codepoints in `target`.\n   */\n  public decode(input: Uint8Array, target: Uint32Array): number {\n    const length = input.length;\n\n    if (!length) {\n      return 0;\n    }\n\n    let size = 0;\n    let byte1: number;\n    let byte2: number;\n    let byte3: number;\n    let byte4: number;\n    let codepoint = 0;\n    let startPos = 0;\n\n    // handle leftover bytes\n    if (this.interim[0]) {\n      let discardInterim = false;\n      let cp = this.interim[0];\n      cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);\n      let pos = 0;\n      let tmp: number;\n      while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) {\n        cp <<= 6;\n        cp |= tmp;\n      }\n      // missing bytes - read ahead from input\n      const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;\n      const missing = type - pos;\n      while (startPos < missing) {\n        if (startPos >= length) {\n          return 0;\n        }\n        tmp = input[startPos++];\n        if ((tmp & 0xC0) !== 0x80) {\n          // wrong continuation, discard interim bytes completely\n          startPos--;\n          discardInterim = true;\n          break;\n        } else {\n          // need to save so we can continue short inputs in next call\n          this.interim[pos++] = tmp;\n          cp <<= 6;\n          cp |= tmp & 0x3F;\n        }\n      }\n      if (!discardInterim) {\n        // final test is type dependent\n        if (type === 2) {\n          if (cp < 0x80) {\n            // wrong starter byte\n            startPos--;\n          } else {\n            target[size++] = cp;\n          }\n        } else if (type === 3) {\n          if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF) || cp === 0xFEFF) {\n            // illegal codepoint or BOM\n          } else {\n            target[size++] = cp;\n          }\n        } else {\n          if (cp < 0x010000 || cp > 0x10FFFF) {\n            // illegal codepoint\n          } else {\n            target[size++] = cp;\n          }\n        }\n      }\n      this.interim.fill(0);\n    }\n\n    // loop through input\n    const fourStop = length - 4;\n    let i = startPos;\n    while (i < length) {\n      /**\n       * ASCII shortcut with loop unrolled to 4 consecutive ASCII chars.\n       * This is a compromise between speed gain for ASCII\n       * and penalty for non ASCII:\n       * For best ASCII performance the char should be stored directly into target,\n       * but even a single attempt to write to target and compare afterwards\n       * penalizes non ASCII really bad (-50%), thus we load the char into byteX first,\n       * which reduces ASCII performance by ~15%.\n       * This trial for ASCII reduces non ASCII performance by ~10% which seems acceptible\n       * compared to the gains.\n       * Note that this optimization only takes place for 4 consecutive ASCII chars,\n       * for any shorter it bails out. Worst case - all 4 bytes being read but\n       * thrown away due to the last being a non ASCII char (-10% performance).\n       */\n      while (i < fourStop\n        && !((byte1 = input[i]) & 0x80)\n        && !((byte2 = input[i + 1]) & 0x80)\n        && !((byte3 = input[i + 2]) & 0x80)\n        && !((byte4 = input[i + 3]) & 0x80))\n      {\n        target[size++] = byte1;\n        target[size++] = byte2;\n        target[size++] = byte3;\n        target[size++] = byte4;\n        i += 4;\n      }\n\n      // reread byte1\n      byte1 = input[i++];\n\n      // 1 byte\n      if (byte1 < 0x80) {\n        target[size++] = byte1;\n\n        // 2 bytes\n      } else if ((byte1 & 0xE0) === 0xC0) {\n        if (i >= length) {\n          this.interim[0] = byte1;\n          return size;\n        }\n        byte2 = input[i++];\n        if ((byte2 & 0xC0) !== 0x80) {\n          // wrong continuation\n          i--;\n          continue;\n        }\n        codepoint = (byte1 & 0x1F) << 6 | (byte2 & 0x3F);\n        if (codepoint < 0x80) {\n          // wrong starter byte\n          i--;\n          continue;\n        }\n        target[size++] = codepoint;\n\n        // 3 bytes\n      } else if ((byte1 & 0xF0) === 0xE0) {\n        if (i >= length) {\n          this.interim[0] = byte1;\n          return size;\n        }\n        byte2 = input[i++];\n        if ((byte2 & 0xC0) !== 0x80) {\n          // wrong continuation\n          i--;\n          continue;\n        }\n        if (i >= length) {\n          this.interim[0] = byte1;\n          this.interim[1] = byte2;\n          return size;\n        }\n        byte3 = input[i++];\n        if ((byte3 & 0xC0) !== 0x80) {\n          // wrong continuation\n          i--;\n          continue;\n        }\n        codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F);\n        if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF) || codepoint === 0xFEFF) {\n          // illegal codepoint or BOM, no i-- here\n          continue;\n        }\n        target[size++] = codepoint;\n\n        // 4 bytes\n      } else if ((byte1 & 0xF8) === 0xF0) {\n        if (i >= length) {\n          this.interim[0] = byte1;\n          return size;\n        }\n        byte2 = input[i++];\n        if ((byte2 & 0xC0) !== 0x80) {\n          // wrong continuation\n          i--;\n          continue;\n        }\n        if (i >= length) {\n          this.interim[0] = byte1;\n          this.interim[1] = byte2;\n          return size;\n        }\n        byte3 = input[i++];\n        if ((byte3 & 0xC0) !== 0x80) {\n          // wrong continuation\n          i--;\n          continue;\n        }\n        if (i >= length) {\n          this.interim[0] = byte1;\n          this.interim[1] = byte2;\n          this.interim[2] = byte3;\n          return size;\n        }\n        byte4 = input[i++];\n        if ((byte4 & 0xC0) !== 0x80) {\n          // wrong continuation\n          i--;\n          continue;\n        }\n        codepoint = (byte1 & 0x07) << 18 | (byte2 & 0x3F) << 12 | (byte3 & 0x3F) << 6 | (byte4 & 0x3F);\n        if (codepoint < 0x010000 || codepoint > 0x10FFFF) {\n          // illegal codepoint, no i-- here\n          continue;\n        }\n        target[size++] = codepoint;\n      } else {\n        // illegal byte, just skip\n      }\n    }\n    return size;\n  }\n}\n"
  },
  {
    "path": "src/common/input/UnicodeV6.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { UnicodeV6 } from 'common/input/UnicodeV6';\n\nit('wcwidth should match all values from the old implementation', function(): void {\n  // new implementation\n  const wcwidthNew = (new UnicodeV6()).wcwidth;\n\n  // old implementation\n  const wcwidthOld = (function(opts: {nul: number, control: number}): (ucs: number) => number {\n    // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n    // combining characters\n    const COMBINING_BMP = [\n      [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n      [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n      [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n      [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n      [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n      [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n      [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n      [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n      [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n      [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n      [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n      [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n      [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n      [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n      [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n      [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n      [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n      [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n      [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n      [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n      [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n      [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n      [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n      [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n      [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n      [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n      [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n      [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n      [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n      [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n      [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n      [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n      [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n      [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n      [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n      [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n      [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n      [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n      [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n      [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n      [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n      [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n      [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]\n    ];\n    const COMBINING_HIGH = [\n      [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n      [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n      [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n      [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n      [0xE0100, 0xE01EF]\n    ];\n    // binary search\n    function bisearch(ucs: number, data: number[][]): boolean {\n      let min = 0;\n      let max = data.length - 1;\n      let mid;\n      if (ucs < data[0][0] || ucs > data[max][1]) {\n        return false;\n      }\n      while (max >= min) {\n        mid = (min + max) >> 1;\n        if (ucs > data[mid][1]) {\n          min = mid + 1;\n        } else if (ucs < data[mid][0]) {\n          max = mid - 1;\n        } else {\n          return true;\n        }\n      }\n      return false;\n    }\n    function wcwidthBMP(ucs: number): number {\n      // test for 8-bit control characters\n      if (ucs === 0) {\n        return opts.nul;\n      }\n      if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) {\n        return opts.control;\n      }\n      // binary search in table of non-spacing characters\n      if (bisearch(ucs, COMBINING_BMP)) {\n        return 0;\n      }\n      // if we arrive here, ucs is not a combining or C0/C1 control character\n      if (isWideBMP(ucs)) {\n        return 2;\n      }\n      return 1;\n    }\n    function isWideBMP(ucs: number): boolean {\n      return (\n        ucs >= 0x1100 && (\n          ucs <= 0x115f ||                // Hangul Jamo init. consonants\n          ucs === 0x2329 ||\n          ucs === 0x232a ||\n          (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) ||  // CJK..Yi\n          (ucs >= 0xac00 && ucs <= 0xd7a3) ||    // Hangul Syllables\n          (ucs >= 0xf900 && ucs <= 0xfaff) ||    // CJK Compat Ideographs\n          (ucs >= 0xfe10 && ucs <= 0xfe19) ||    // Vertical forms\n          (ucs >= 0xfe30 && ucs <= 0xfe6f) ||    // CJK Compat Forms\n          (ucs >= 0xff00 && ucs <= 0xff60) ||    // Fullwidth Forms\n          (ucs >= 0xffe0 && ucs <= 0xffe6)));\n    }\n    function wcwidthHigh(ucs: number): 0 | 1 | 2 {\n      if (bisearch(ucs, COMBINING_HIGH)) {\n        return 0;\n      }\n      if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n        return 2;\n      }\n      return 1;\n    }\n    const control = opts.control | 0;\n    let table: number[] | Uint32Array | undefined;\n    function initTable(): number[] | Uint32Array {\n      // lookup table for BMP\n      const CODEPOINTS = 65536;  // BMP holds 65536 codepoints\n      const BITWIDTH = 2;        // a codepoint can have a width of 0, 1 or 2\n      const ITEMSIZE = 32;       // using uint32_t\n      const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;\n      const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;\n      table = (typeof Uint32Array === 'undefined')\n        ? new Array(CONTAINERSIZE)\n        : new Uint32Array(CONTAINERSIZE);\n      for (let i = 0; i < CONTAINERSIZE; ++i) {\n        let num = 0;\n        let pos = CODEPOINTS_PER_ITEM;\n        while (pos--) {\n          num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);\n        }\n        table[i] = num;\n      }\n      return table;\n    }\n    // get width from lookup table\n    //   position in container   : num / CODEPOINTS_PER_ITEM\n    //     ==> n = table[Math.floor(num / 16)]\n    //     ==> n = table[num >> 4]\n    //   16 codepoints per number:       FFEEDDCCBBAA99887766554433221100\n    //   position in number      : (num % CODEPOINTS_PER_ITEM) * BITWIDTH\n    //     ==> m = (n % 16) * 2\n    //     ==> m = (num & 15) << 1\n    //   right shift to position m\n    //     ==> n = n >> m     e.g. m=12  000000000000FFEEDDCCBBAA99887766\n    //   we are only interested in 2 LSBs, cut off higher bits\n    //     ==> n = n & 3      e.g.       000000000000000000000000000000XX\n    return (num: number): number => {\n      num |= 0;  // get asm.js like optimization under V8\n      if (num < 32) {\n        return control | 0;\n      }\n      if (num < 127) {\n        return 1;\n      }\n      const t = table ?? initTable();\n      if (num < 65536) {\n        return t[num >> 4] >> ((num & 15) << 1) & 3;\n      }\n      // do a full search for high codepoints\n      return wcwidthHigh(num);\n    };\n  })({nul: 0, control: 0}); // configurable options\n\n  // test full BMP range old vs new implmenetation\n  for (let i = 0; i < 65536; ++i) {\n    assert.equal(wcwidthNew(i), wcwidthOld(i), `mismatch for i: ${i}`);\n  }\n});\n"
  },
  {
    "path": "src/common/input/UnicodeV6.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';\nimport { UnicodeService } from 'common/services/UnicodeService';\n\nconst BMP_COMBINING = [\n  [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n  [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n  [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n  [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n  [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n  [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n  [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n  [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n  [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n  [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n  [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n  [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n  [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n  [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n  [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n  [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n  [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n  [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n  [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n  [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n  [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n  [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n  [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n  [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n  [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n  [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n  [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n  [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n  [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n  [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n  [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n  [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n  [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n  [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n  [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n  [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n  [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n  [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n  [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n  [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n  [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n  [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n  [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]\n];\nconst HIGH_COMBINING = [\n  [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n  [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n  [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n  [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n  [0xE0100, 0xE01EF]\n];\n\n// BMP lookup table, lazy initialized during first addon loading\nlet table: Uint8Array;\n\nfunction bisearch(ucs: number, data: number[][]): boolean {\n  let min = 0;\n  let max = data.length - 1;\n  let mid;\n  if (ucs < data[0][0] || ucs > data[max][1]) {\n    return false;\n  }\n  while (max >= min) {\n    mid = (min + max) >> 1;\n    if (ucs > data[mid][1]) {\n      min = mid + 1;\n    } else if (ucs < data[mid][0]) {\n      max = mid - 1;\n    } else {\n      return true;\n    }\n  }\n  return false;\n}\n\nexport class UnicodeV6 implements IUnicodeVersionProvider {\n  public readonly version = '6';\n\n  constructor() {\n    // init lookup table once\n    if (!table) {\n      table = new Uint8Array(65536);\n      table.fill(1);\n      table[0] = 0;\n      // control chars\n      table.fill(0, 1, 32);\n      table.fill(0, 0x7f, 0xa0);\n\n      // apply wide char rules first\n      // wide chars\n      table.fill(2, 0x1100, 0x1160);\n      table[0x2329] = 2;\n      table[0x232a] = 2;\n      table.fill(2, 0x2e80, 0xa4d0);\n      table[0x303f] = 1;  // wrongly in last line\n\n      table.fill(2, 0xac00, 0xd7a4);\n      table.fill(2, 0xf900, 0xfb00);\n      table.fill(2, 0xfe10, 0xfe1a);\n      table.fill(2, 0xfe30, 0xfe70);\n      table.fill(2, 0xff00, 0xff61);\n      table.fill(2, 0xffe0, 0xffe7);\n\n      // apply combining last to ensure we overwrite\n      // wrongly wide set chars:\n      //    the original algo evals combining first and falls\n      //    through to wide check so we simply do here the opposite\n      // combining 0\n      for (let r = 0; r < BMP_COMBINING.length; ++r) {\n        table.fill(0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1);\n      }\n    }\n  }\n\n  public wcwidth(num: number): UnicodeCharWidth {\n    if (num < 32) return 0;\n    if (num < 127) return 1;\n    if (num < 65536) return table[num] as UnicodeCharWidth;\n    if (bisearch(num, HIGH_COMBINING)) return 0;\n    if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2;\n    return 1;\n  }\n\n  public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {\n    let width = this.wcwidth(codepoint);\n    let shouldJoin = width === 0 && preceding !== 0;\n    if (shouldJoin) {\n      const oldWidth = UnicodeService.extractWidth(preceding);\n      if (oldWidth === 0) {\n        shouldJoin = false;\n      } else if (oldWidth > width) {\n        width = oldWidth;\n      }\n    }\n    return UnicodeService.createPropertyValue(0, width, shouldJoin);\n  }\n}\n"
  },
  {
    "path": "src/common/input/Win32InputMode.test.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { Win32InputMode, Win32ControlKeyState } from 'common/input/Win32InputMode';\nimport { IKeyboardEvent, KeyboardResultType } from 'common/Types';\n\ntype EventOpts = Partial<IKeyboardEvent>;\nconst ev = (opts: EventOpts): IKeyboardEvent => ({\n  altKey: false, ctrlKey: false, shiftKey: false, metaKey: false,\n  keyCode: 0, code: '', key: '', type: 'keydown', ...opts\n});\n\nconst parse = (seq: string) => {\n  const m = seq.match(/^\\x1b\\[(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+)_$/);\n  return m ? { vk: +m[1], sc: +m[2], uc: +m[3], kd: +m[4], cs: +m[5], rc: +m[6] } : null;\n};\n\nconst win32 = new Win32InputMode();\n\nconst test = (opts: EventOpts, isDown: boolean, check: (p: ReturnType<typeof parse>) => void) => {\n  const result = win32.evaluateKeyboardEvent(ev(opts), isDown);\n  const parsed = parse(result.key!);\n  assert.ok(parsed);\n  check(parsed);\n};\n\ndescribe('Win32InputMode', () => {\n  describe('evaluateKeyboardEvent', () => {\n    describe('basic key encoding', () => {\n      it('letter key press', () => {\n        const result = win32.evaluateKeyboardEvent(ev({ code: 'KeyA', key: 'a', keyCode: 65 }), true);\n        assert.strictEqual(result.type, KeyboardResultType.SEND_KEY);\n        assert.strictEqual(result.cancel, true);\n        const p = parse(result.key!);\n        assert.ok(p);\n        assert.deepStrictEqual([p.vk, p.uc, p.kd, p.rc], [0x41, 97, 1, 1]);\n      });\n      it('letter key release', () => test({ code: 'KeyA', key: 'a', keyCode: 65 }, false, p => assert.strictEqual(p!.kd, 0)));\n      it('digit key', () => test({ code: 'Digit1', key: '1', keyCode: 49 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x31, 49])));\n      it('Enter key', () => test({ code: 'Enter', key: 'Enter', keyCode: 13 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x0D, 13])));\n      it('Escape key', () => test({ code: 'Escape', key: 'Escape', keyCode: 27 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x1B, 27])));\n      it('Space key', () => test({ code: 'Space', key: ' ', keyCode: 32 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x20, 32])));\n    });\n\n    describe('modifier encoding', () => {\n      it('shift', () => test({ code: 'KeyA', key: 'A', keyCode: 65, shiftKey: true }, true, p => assert.ok(p!.cs & Win32ControlKeyState.SHIFT_PRESSED)));\n      it('ctrl left', () => test({ code: 'KeyA', key: 'a', keyCode: 65, ctrlKey: true }, true, p => assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED)));\n      it('ctrl right', () => test({ code: 'ControlRight', key: 'Control', keyCode: 17, ctrlKey: true }, true, p => {\n        assert.ok(p!.cs & Win32ControlKeyState.RIGHT_CTRL_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('alt left', () => test({ code: 'KeyA', key: 'a', keyCode: 65, altKey: true }, true, p => assert.ok(p!.cs & Win32ControlKeyState.LEFT_ALT_PRESSED)));\n      it('alt right', () => test({ code: 'AltRight', key: 'Alt', keyCode: 18, altKey: true }, true, p => {\n        assert.ok(p!.cs & Win32ControlKeyState.RIGHT_ALT_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('multiple modifiers', () => test({ code: 'KeyA', key: 'A', keyCode: 65, shiftKey: true, ctrlKey: true, altKey: true }, true, p => {\n        assert.ok(p!.cs & Win32ControlKeyState.SHIFT_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_ALT_PRESSED);\n      }));\n    });\n\n    describe('function keys', () => {\n      it('F1', () => test({ code: 'F1', key: 'F1', keyCode: 112 }, true, p => assert.strictEqual(p!.vk, 0x70)));\n      it('F5', () => test({ code: 'F5', key: 'F5', keyCode: 116 }, true, p => assert.strictEqual(p!.vk, 0x74)));\n      it('F12', () => test({ code: 'F12', key: 'F12', keyCode: 123 }, true, p => assert.strictEqual(p!.vk, 0x7B)));\n      it('Ctrl+F1', () => test({ code: 'F1', key: 'F1', keyCode: 112, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x70);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n      }));\n    });\n\n    describe('navigation keys (ENHANCED_KEY)', () => {\n      const navKeys: [string, string, number, number][] = [\n        ['ArrowUp', 'ArrowUp', 38, 0x26],\n        ['ArrowDown', 'ArrowDown', 40, 0x28],\n        ['ArrowLeft', 'ArrowLeft', 37, 0x25],\n        ['ArrowRight', 'ArrowRight', 39, 0x27],\n        ['Home', 'Home', 36, 0x24],\n        ['End', 'End', 35, 0x23],\n        ['PageUp', 'PageUp', 33, 0x21],\n        ['PageDown', 'PageDown', 34, 0x22],\n        ['Insert', 'Insert', 45, 0x2D],\n        ['Delete', 'Delete', 46, 0x2E],\n      ];\n      navKeys.forEach(([code, key, keyCode, vk]) => {\n        it(code, () => test({ code, key, keyCode }, true, p => {\n          assert.strictEqual(p!.vk, vk);\n          assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n        }));\n      });\n      it('Tab', () => test({ code: 'Tab', key: 'Tab', keyCode: 9 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x09, 9])));\n      it('Backspace', () => test({ code: 'Backspace', key: 'Backspace', keyCode: 8 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x08, 8])));\n    });\n\n    describe('numpad keys', () => {\n      it('Numpad0', () => test({ code: 'Numpad0', key: '0', keyCode: 96 }, true, p => assert.strictEqual(p!.vk, 0x60)));\n      it('NumpadEnter (ENHANCED)', () => test({ code: 'NumpadEnter', key: 'Enter', keyCode: 13 }, true, p => {\n        assert.strictEqual(p!.vk, 0x0D);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('NumpadAdd', () => test({ code: 'NumpadAdd', key: '+', keyCode: 107 }, true, p => assert.strictEqual(p!.vk, 0x6B)));\n      it('NumpadSubtract', () => test({ code: 'NumpadSubtract', key: '-', keyCode: 109 }, true, p => assert.strictEqual(p!.vk, 0x6D)));\n      it('NumpadMultiply', () => test({ code: 'NumpadMultiply', key: '*', keyCode: 106 }, true, p => assert.strictEqual(p!.vk, 0x6A)));\n      it('NumpadDivide (ENHANCED)', () => test({ code: 'NumpadDivide', key: '/', keyCode: 111 }, true, p => {\n        assert.strictEqual(p!.vk, 0x6F);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('NumpadDecimal', () => test({ code: 'NumpadDecimal', key: '.', keyCode: 110 }, true, p => assert.strictEqual(p!.vk, 0x6E)));\n    });\n\n    describe('unicode character', () => {\n      it('printable', () => test({ code: 'KeyA', key: 'a', keyCode: 65 }, true, p => assert.strictEqual(p!.uc, 97)));\n      it('shifted', () => test({ code: 'KeyA', key: 'A', keyCode: 65, shiftKey: true }, true, p => assert.strictEqual(p!.uc, 65)));\n      it('non-printable is 0', () => test({ code: 'ArrowUp', key: 'ArrowUp', keyCode: 38 }, true, p => assert.strictEqual(p!.uc, 0)));\n      it('extended ASCII', () => test({ code: 'KeyE', key: 'é', keyCode: 69 }, true, p => assert.strictEqual(p!.uc, 233)));\n      it('symbol', () => test({ code: 'Digit4', key: '$', keyCode: 52, shiftKey: true }, true, p => assert.strictEqual(p!.uc, 36)));\n    });\n\n    describe('ctrl+letter control characters', () => {\n      it('Ctrl+A produces 0x01', () => test({ code: 'KeyA', key: 'a', keyCode: 65, ctrlKey: true }, true, p => assert.strictEqual(p!.uc, 0x01)));\n      it('Ctrl+C produces 0x03 (ETX)', () => test({ code: 'KeyC', key: 'c', keyCode: 67, ctrlKey: true }, true, p => assert.strictEqual(p!.uc, 0x03)));\n      it('Ctrl+Z produces 0x1A', () => test({ code: 'KeyZ', key: 'z', keyCode: 90, ctrlKey: true }, true, p => assert.strictEqual(p!.uc, 0x1A)));\n      it('Ctrl+Shift+A (uppercase) produces 0x01', () => test({ code: 'KeyA', key: 'A', keyCode: 65, ctrlKey: true, shiftKey: true }, true, p => assert.strictEqual(p!.uc, 0x01)));\n      it('Ctrl+Shift+C (uppercase) produces 0x03', () => test({ code: 'KeyC', key: 'C', keyCode: 67, ctrlKey: true, shiftKey: true }, true, p => assert.strictEqual(p!.uc, 0x03)));\n      it('Ctrl+Alt+C does not produce control char', () => test({ code: 'KeyC', key: 'c', keyCode: 67, ctrlKey: true, altKey: true }, true, p => assert.strictEqual(p!.uc, 99)));\n    });\n\n    describe('scan codes', () => {\n      it('letter A', () => test({ code: 'KeyA', key: 'a', keyCode: 65 }, true, p => assert.strictEqual(p!.sc, 0x1E)));\n      it('Escape', () => test({ code: 'Escape', key: 'Escape', keyCode: 27 }, true, p => assert.strictEqual(p!.sc, 0x01)));\n    });\n\n    describe('sequence format', () => {\n      it('valid CSI format', () => {\n        const result = win32.evaluateKeyboardEvent(ev({ code: 'KeyA', key: 'a', keyCode: 65 }), true);\n        assert.ok(result.key?.startsWith('\\x1b[') && result.key.endsWith('_'));\n        assert.strictEqual(result.key?.slice(2, -1).split(';').length, 6);\n      });\n    });\n\n    describe('standalone modifier keys', () => {\n      it('ShiftLeft', () => test({ code: 'ShiftLeft', key: 'Shift', keyCode: 16, shiftKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x10);\n        assert.ok(p!.cs & Win32ControlKeyState.SHIFT_PRESSED);\n      }));\n      it('ShiftRight', () => test({ code: 'ShiftRight', key: 'Shift', keyCode: 16, shiftKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x10);\n        assert.ok(p!.cs & Win32ControlKeyState.SHIFT_PRESSED);\n      }));\n      it('ControlLeft', () => test({ code: 'ControlLeft', key: 'Control', keyCode: 17, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x11);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n      }));\n      it('ControlRight', () => test({ code: 'ControlRight', key: 'Control', keyCode: 17, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x11);\n        assert.ok(p!.cs & Win32ControlKeyState.RIGHT_CTRL_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('AltLeft', () => test({ code: 'AltLeft', key: 'Alt', keyCode: 18, altKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x12);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_ALT_PRESSED);\n      }));\n      it('AltRight', () => test({ code: 'AltRight', key: 'Alt', keyCode: 18, altKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x12);\n        assert.ok(p!.cs & Win32ControlKeyState.RIGHT_ALT_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('modifier release', () => test({ code: 'ShiftLeft', key: 'Shift', keyCode: 16 }, false, p => assert.strictEqual(p!.kd, 0)));\n    });\n\n    describe('problem keys from spec', () => {\n      it('Ctrl+Space', () => test({ code: 'Space', key: ' ', keyCode: 32, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x20);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n      }));\n      it('Shift+Enter', () => test({ code: 'Enter', key: 'Enter', keyCode: 13, shiftKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x0D);\n        assert.ok(p!.cs & Win32ControlKeyState.SHIFT_PRESSED);\n      }));\n      it('Ctrl+Break', () => test({ code: 'Pause', key: 'Pause', keyCode: 19, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x13);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n      }));\n      it('Ctrl+Alt+/', () => test({ code: 'Slash', key: '/', keyCode: 191, ctrlKey: true, altKey: true }, true, p => {\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_ALT_PRESSED);\n      }));\n      it('Ctrl+Enter produces LF (0x0A)', () => test({ code: 'Enter', key: 'Enter', keyCode: 13, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x0D);\n        assert.strictEqual(p!.uc, 0x0A);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n      }));\n      it('Ctrl+Backspace produces DEL (0x7F)', () => test({ code: 'Backspace', key: 'Backspace', keyCode: 8, ctrlKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x08);\n        assert.strictEqual(p!.uc, 0x7F);\n        assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED);\n      }));\n    });\n\n    describe('meta key', () => {\n      it('MetaLeft', () => test({ code: 'MetaLeft', key: 'Meta', keyCode: 91, metaKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x5B);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n      it('MetaRight', () => test({ code: 'MetaRight', key: 'Meta', keyCode: 92, metaKey: true }, true, p => {\n        assert.strictEqual(p!.vk, 0x5C);\n        assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY);\n      }));\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/input/Win32InputMode.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Win32 input mode implementation.\n * @see https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md\n *\n * Format: CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _\n *   Vk: Virtual key code (decimal)\n *   Sc: Scan code (decimal)\n *   Uc: Unicode character (decimal codepoint, 0 if none)\n *   Kd: Key down (1) or up (0)\n *   Cs: Control key state (modifier flags)\n *   Rc: Repeat count (usually 1)\n */\n\nimport { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types';\nimport { C0 } from 'common/data/EscapeSequences';\n\n/**\n * Win32 control key state flags (from Windows API).\n */\nexport const enum Win32ControlKeyState {\n  RIGHT_ALT_PRESSED   = 0b000000001,\n  LEFT_ALT_PRESSED    = 0b000000010,\n  RIGHT_CTRL_PRESSED  = 0b000000100,\n  LEFT_CTRL_PRESSED   = 0b000001000,\n  SHIFT_PRESSED       = 0b000010000,\n  NUMLOCK_ON          = 0b000100000,\n  SCROLLLOCK_ON       = 0b001000000,\n  CAPSLOCK_ON         = 0b010000000,\n  ENHANCED_KEY        = 0b100000000,\n}\n\n/**\n * Win32 input mode handler. Lookup tables are only initialized when this class\n * is instantiated, reducing bundle size for environments that don't use this mode.\n */\nexport class Win32InputMode {\n  /**\n   * Mapping from browser KeyboardEvent.code to Win32 virtual key codes.\n   * Based on https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes\n   */\n  private readonly _codeToVk: { [code: string]: number } = {\n    // Letters\n    'KeyA': 0x41, 'KeyB': 0x42, 'KeyC': 0x43, 'KeyD': 0x44, 'KeyE': 0x45,\n    'KeyF': 0x46, 'KeyG': 0x47, 'KeyH': 0x48, 'KeyI': 0x49, 'KeyJ': 0x4A,\n    'KeyK': 0x4B, 'KeyL': 0x4C, 'KeyM': 0x4D, 'KeyN': 0x4E, 'KeyO': 0x4F,\n    'KeyP': 0x50, 'KeyQ': 0x51, 'KeyR': 0x52, 'KeyS': 0x53, 'KeyT': 0x54,\n    'KeyU': 0x55, 'KeyV': 0x56, 'KeyW': 0x57, 'KeyX': 0x58, 'KeyY': 0x59,\n    'KeyZ': 0x5A,\n\n    // Digits\n    'Digit0': 0x30, 'Digit1': 0x31, 'Digit2': 0x32, 'Digit3': 0x33, 'Digit4': 0x34,\n    'Digit5': 0x35, 'Digit6': 0x36, 'Digit7': 0x37, 'Digit8': 0x38, 'Digit9': 0x39,\n\n    // Function keys\n    'F1': 0x70, 'F2': 0x71, 'F3': 0x72, 'F4': 0x73, 'F5': 0x74, 'F6': 0x75,\n    'F7': 0x76, 'F8': 0x77, 'F9': 0x78, 'F10': 0x79, 'F11': 0x7A, 'F12': 0x7B,\n    'F13': 0x7C, 'F14': 0x7D, 'F15': 0x7E, 'F16': 0x7F, 'F17': 0x80, 'F18': 0x81,\n    'F19': 0x82, 'F20': 0x83, 'F21': 0x84, 'F22': 0x85, 'F23': 0x86, 'F24': 0x87,\n\n    // Numpad\n    'Numpad0': 0x60, 'Numpad1': 0x61, 'Numpad2': 0x62, 'Numpad3': 0x63, 'Numpad4': 0x64,\n    'Numpad5': 0x65, 'Numpad6': 0x66, 'Numpad7': 0x67, 'Numpad8': 0x68, 'Numpad9': 0x69,\n    'NumpadMultiply': 0x6A, 'NumpadAdd': 0x6B, 'NumpadSeparator': 0x6C,\n    'NumpadSubtract': 0x6D, 'NumpadDecimal': 0x6E, 'NumpadDivide': 0x6F,\n    'NumpadEnter': 0x0D, // Same as Enter but with ENHANCED_KEY flag\n    'NumLock': 0x90,\n\n    // Navigation\n    'ArrowUp': 0x26, 'ArrowDown': 0x28, 'ArrowLeft': 0x25, 'ArrowRight': 0x27,\n    'Home': 0x24, 'End': 0x23, 'PageUp': 0x21, 'PageDown': 0x22,\n    'Insert': 0x2D, 'Delete': 0x2E,\n\n    // Modifiers\n    'ShiftLeft': 0x10, 'ShiftRight': 0x10,\n    'ControlLeft': 0x11, 'ControlRight': 0x11,\n    'AltLeft': 0x12, 'AltRight': 0x12,\n    'MetaLeft': 0x5B, 'MetaRight': 0x5C,\n    'CapsLock': 0x14, 'ScrollLock': 0x91,\n\n    // Special keys\n    'Escape': 0x1B, 'Enter': 0x0D, 'Tab': 0x09, 'Space': 0x20,\n    'Backspace': 0x08, 'Pause': 0x13, 'ContextMenu': 0x5D, 'PrintScreen': 0x2C,\n\n    // OEM keys (US keyboard layout)\n    'Semicolon': 0xBA,      // ;:\n    'Equal': 0xBB,          // =+\n    'Comma': 0xBC,          // ,<\n    'Minus': 0xBD,          // -_\n    'Period': 0xBE,         // .>\n    'Slash': 0xBF,          // /?\n    'Backquote': 0xC0,      // `~\n    'BracketLeft': 0xDB,    // [{\n    'Backslash': 0xDC,      // \\|\n    'BracketRight': 0xDD,   // ]}\n    'Quote': 0xDE,          // '\"\n    'IntlBackslash': 0xE2   // Non-US backslash\n  };\n\n  /**\n   * Mapping from browser KeyboardEvent.code to approximate Win32 scan codes.\n   * Note: Scan codes can vary by keyboard layout. These are approximations\n   * based on standard US keyboard layout.\n   */\n  private readonly _codeToScancode: { [code: string]: number } = {\n    // Letters (row by row)\n    'KeyQ': 0x10, 'KeyW': 0x11, 'KeyE': 0x12, 'KeyR': 0x13, 'KeyT': 0x14,\n    'KeyY': 0x15, 'KeyU': 0x16, 'KeyI': 0x17, 'KeyO': 0x18, 'KeyP': 0x19,\n    'KeyA': 0x1E, 'KeyS': 0x1F, 'KeyD': 0x20, 'KeyF': 0x21, 'KeyG': 0x22,\n    'KeyH': 0x23, 'KeyJ': 0x24, 'KeyK': 0x25, 'KeyL': 0x26,\n    'KeyZ': 0x2C, 'KeyX': 0x2D, 'KeyC': 0x2E, 'KeyV': 0x2F, 'KeyB': 0x30,\n    'KeyN': 0x31, 'KeyM': 0x32,\n\n    // Digits\n    'Digit1': 0x02, 'Digit2': 0x03, 'Digit3': 0x04, 'Digit4': 0x05, 'Digit5': 0x06,\n    'Digit6': 0x07, 'Digit7': 0x08, 'Digit8': 0x09, 'Digit9': 0x0A, 'Digit0': 0x0B,\n\n    // Function keys\n    'F1': 0x3B, 'F2': 0x3C, 'F3': 0x3D, 'F4': 0x3E, 'F5': 0x3F, 'F6': 0x40,\n    'F7': 0x41, 'F8': 0x42, 'F9': 0x43, 'F10': 0x44, 'F11': 0x57, 'F12': 0x58,\n\n    // Numpad\n    'Numpad0': 0x52, 'Numpad1': 0x4F, 'Numpad2': 0x50, 'Numpad3': 0x51, 'Numpad4': 0x4B,\n    'Numpad5': 0x4C, 'Numpad6': 0x4D, 'Numpad7': 0x47, 'Numpad8': 0x48, 'Numpad9': 0x49,\n    'NumpadMultiply': 0x37, 'NumpadAdd': 0x4E, 'NumpadSubtract': 0x4A,\n    'NumpadDecimal': 0x53, 'NumpadDivide': 0x35, 'NumpadEnter': 0x1C,\n    'NumLock': 0x45,\n\n    // Navigation (extended keys)\n    'ArrowUp': 0x48, 'ArrowDown': 0x50, 'ArrowLeft': 0x4B, 'ArrowRight': 0x4D,\n    'Home': 0x47, 'End': 0x4F, 'PageUp': 0x49, 'PageDown': 0x51,\n    'Insert': 0x52, 'Delete': 0x53,\n\n    // Modifiers\n    'ShiftLeft': 0x2A, 'ShiftRight': 0x36,\n    'ControlLeft': 0x1D, 'ControlRight': 0x1D,\n    'AltLeft': 0x38, 'AltRight': 0x38,\n    'CapsLock': 0x3A, 'ScrollLock': 0x46,\n\n    // Special keys\n    'Escape': 0x01, 'Enter': 0x1C, 'Tab': 0x0F, 'Space': 0x39,\n    'Backspace': 0x0E, 'Pause': 0x45,\n\n    // OEM keys\n    'Semicolon': 0x27, 'Equal': 0x0D, 'Comma': 0x33, 'Minus': 0x0C,\n    'Period': 0x34, 'Slash': 0x35, 'Backquote': 0x29,\n    'BracketLeft': 0x1A, 'Backslash': 0x2B, 'BracketRight': 0x1B, 'Quote': 0x28\n  };\n\n  /**\n   * Codes that represent enhanced keys (extended keyboard keys).\n   */\n  private readonly _enhancedKeyCodes = new Set([\n    'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',\n    'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete',\n    'NumpadEnter', 'NumpadDivide',\n    'ControlRight', 'AltRight',\n    'PrintScreen', 'Pause', 'ContextMenu',\n    'MetaLeft', 'MetaRight'\n  ]);\n\n  /**\n   * Mapping of special keys (ev.key values) to their Unicode control character codes.\n   * These keys have multi-character ev.key strings but produce control characters.\n   * @see https://docs.microsoft.com/en-us/windows/console/key-event-record-str\n   */\n  private readonly _keyToControlChar: { [key: string]: number } = {\n    'Enter': 0x0D,      // Carriage return\n    'Backspace': 0x08,  // Backspace\n    'Tab': 0x09,        // Horizontal tab\n    'Escape': 0x1B      // Escape\n  };\n\n  /**\n   * Get the Win32 virtual key code for a keyboard event.\n   */\n  private _getVirtualKeyCode(ev: IKeyboardEvent): number {\n    const vk = this._codeToVk[ev.code];\n    if (vk !== undefined) {\n      return vk;\n    }\n    // Fall back to keyCode for unmapped keys\n    return ev.keyCode || 0;\n  }\n\n  /**\n   * Get the Win32 scan code for a keyboard event.\n   * Returns 0 if unknown (scan codes vary by hardware).\n   */\n  private _getScanCode(ev: IKeyboardEvent): number {\n    return this._codeToScancode[ev.code] || 0;\n  }\n\n  /**\n   * Get the unicode character for a keyboard event.\n   * Returns 0 for non-character keys.\n   */\n  private _getUnicodeChar(ev: IKeyboardEvent): number {\n    // Handle special keys that produce control characters\n    // Ctrl modifies some of these: Ctrl+Enter=LF, Ctrl+Backspace=DEL\n    if (ev.ctrlKey && !ev.altKey && !ev.metaKey) {\n      if (ev.key === 'Enter') {\n        return 0x0A; // Line feed (Ctrl+Enter)\n      }\n      if (ev.key === 'Backspace') {\n        return 0x7F; // DEL (Ctrl+Backspace)\n      }\n    }\n\n    // Check for special keys that always produce control characters\n    const controlChar = this._keyToControlChar[ev.key];\n    if (controlChar !== undefined) {\n      return controlChar;\n    }\n\n    // Only single-character keys produce unicode output\n    if (ev.key.length === 1) {\n      const codePoint = ev.key.codePointAt(0) || 0;\n\n      // Handle Ctrl+letter combinations - these produce control characters (0x01-0x1A)\n      if (ev.ctrlKey && !ev.altKey && !ev.metaKey) {\n        // Convert A-Z or a-z to control character (Ctrl+A = 0x01, Ctrl+C = 0x03, etc.)\n        if (codePoint >= 0x41 && codePoint <= 0x5A) { // A-Z\n          return codePoint - 0x40;\n        }\n        if (codePoint >= 0x61 && codePoint <= 0x7A) { // a-z\n          return codePoint - 0x60;\n        }\n      }\n\n      return codePoint;\n    }\n    return 0;\n  }\n\n  /**\n   * Get the Win32 control key state flags.\n   */\n  private _getControlKeyState(ev: IKeyboardEvent): number {\n    let state = 0;\n\n    if (ev.shiftKey) {\n      state |= Win32ControlKeyState.SHIFT_PRESSED;\n    }\n\n    // Note: We can't distinguish left/right for ctrl/alt in standard browser events,\n    // so we use the generic pressed flags. The right-side flags are used when\n    // we can detect them (e.g., via code property).\n    if (ev.ctrlKey) {\n      if (ev.code === 'ControlRight') {\n        state |= Win32ControlKeyState.RIGHT_CTRL_PRESSED;\n      } else {\n        state |= Win32ControlKeyState.LEFT_CTRL_PRESSED;\n      }\n    }\n\n    if (ev.altKey) {\n      if (ev.code === 'AltRight') {\n        state |= Win32ControlKeyState.RIGHT_ALT_PRESSED;\n      } else {\n        state |= Win32ControlKeyState.LEFT_ALT_PRESSED;\n      }\n    }\n\n    // Check for enhanced key\n    if (this._enhancedKeyCodes.has(ev.code)) {\n      state |= Win32ControlKeyState.ENHANCED_KEY;\n    }\n\n    return state;\n  }\n\n  /**\n   * Evaluate a keyboard event using Win32 input mode.\n   *\n   * @param ev The keyboard event.\n   * @param isKeyDown Whether this is a keydown (true) or keyup (false) event.\n   * @returns The keyboard result with the encoded key sequence.\n   */\n  public evaluateKeyboardEvent(ev: IKeyboardEvent, isKeyDown: boolean): IKeyboardResult {\n    const vk = this._getVirtualKeyCode(ev);\n    const sc = this._getScanCode(ev);\n    const uc = this._getUnicodeChar(ev);\n    const kd = isKeyDown ? 1 : 0;\n    const cs = this._getControlKeyState(ev);\n    const rc = 1; // Repeat count, always 1 for now\n\n    // Format: CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _\n    return {\n      type: KeyboardResultType.SEND_KEY,\n      cancel: true,\n      key: `${C0.ESC}[${vk};${sc};${uc};${kd};${cs};${rc}_`\n    };\n  }\n}\n"
  },
  {
    "path": "src/common/input/WriteBuffer.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { WriteBuffer } from './WriteBuffer';\n\n// eslint-disable-next-line @typescript-eslint/naming-convention\ndeclare let Buffer: any;\n\nfunction toBytes(s: string): Uint8Array {\n  return Buffer.from(s);\n}\n\nfunction fromBytes(bytes: Uint8Array): string {\n  return bytes.toString();\n}\n\ndescribe('WriteBuffer', () => {\n  let wb: WriteBuffer;\n  let stack: (string | Uint8Array)[] = [];\n  let cbStack: string[] = [];\n  beforeEach(() => {\n    stack = [];\n    cbStack = [];\n    wb = new WriteBuffer(data => { stack.push(data); });\n  });\n  describe('write input', () => {\n    it('string', done => {\n      wb.write('a._');\n      wb.write('b.x', () => { cbStack.push('b'); });\n      wb.write('c._');\n      wb.write('d.x', () => { cbStack.push('d'); });\n      wb.write('e', () => {\n        assert.deepEqual(stack, ['a._', 'b.x', 'c._', 'd.x', 'e']);\n        assert.deepEqual(cbStack, ['b', 'd']);\n        done();\n      });\n    });\n    it('bytes', done => {\n      wb.write(toBytes('a._'));\n      wb.write(toBytes('b.x'), () => { cbStack.push('b'); });\n      wb.write(toBytes('c._'));\n      wb.write(toBytes('d.x'), () => { cbStack.push('d'); });\n      wb.write(toBytes('e'), () => {\n        assert.deepEqual(stack.map(val => typeof val === 'string' ? '' :  fromBytes(val)), ['a._', 'b.x', 'c._', 'd.x', 'e']);\n        assert.deepEqual(cbStack, ['b', 'd']);\n        done();\n      });\n    });\n    it('string/bytes mixed', done => {\n      wb.write('a._');\n      wb.write('b.x', () => { cbStack.push('b'); });\n      wb.write(toBytes('c._'));\n      wb.write(toBytes('d.x'), () => { cbStack.push('d'); });\n      wb.write(toBytes('e'), () => {\n        assert.deepEqual(stack.map(val => typeof val === 'string' ? val :  fromBytes(val)), ['a._', 'b.x', 'c._', 'd.x', 'e']);\n        assert.deepEqual(cbStack, ['b', 'd']);\n        done();\n      });\n    });\n    it('write callback works for empty chunks', done => {\n      wb.write('a', () => { cbStack.push('a'); });\n      wb.write('', () => { cbStack.push('b'); });\n      wb.write(toBytes('c'), () => { cbStack.push('c'); });\n      wb.write(new Uint8Array(0), () => { cbStack.push('d'); });\n      wb.write('e', () => {\n        assert.deepEqual(stack.map(val => typeof val === 'string' ? val :  fromBytes(val)), ['a', '', 'c', '', 'e']);\n        assert.deepEqual(cbStack, ['a', 'b', 'c', 'd']);\n        done();\n      });\n    });\n    it('writeSync', done => {\n      wb.write('a', () => { cbStack.push('a'); });\n      wb.write('b', () => { cbStack.push('b'); });\n      wb.write('c', () => { cbStack.push('c'); });\n      wb.writeSync('d');\n      assert.deepEqual(stack, ['a', 'b', 'c', 'd']);\n      assert.deepEqual(cbStack, ['a', 'b', 'c']);\n      wb.write('x', () => { cbStack.push('x'); });\n      wb.write('', () => {\n        assert.deepEqual(stack, ['a', 'b', 'c', 'd', 'x', '']);\n        assert.deepEqual(cbStack, ['a', 'b', 'c', 'x']);\n        done();\n      });\n    });\n    it('writeSync called from action does not overflow callstack - issue #3265', () => {\n      wb = new WriteBuffer(data => {\n        const num = parseInt(data as string);\n        if (num < 10000) {\n          wb.writeSync('' + (num + 1));\n        }\n      });\n      wb.writeSync('1');\n    });\n    it('writeSync maxSubsequentCalls argument', () => {\n      let last: string = '';\n      wb = new WriteBuffer(data => {\n        last = data as string;\n        const num = parseInt(data as string);\n        if (num < 1000000) {\n          wb.writeSync('' + (num + 1), 10);\n        }\n      });\n      wb.writeSync('1', 10);\n      assert.equal(last, '11'); // 1 + 10 sub calls = 11\n    });\n    it('flushSync processes all pending writes', done => {\n      wb.write('a', () => { cbStack.push('a'); });\n      wb.write('b', () => { cbStack.push('b'); });\n      wb.write('c', () => { cbStack.push('c'); });\n      wb.flushSync();\n      assert.deepEqual(stack, ['a', 'b', 'c']);\n      assert.deepEqual(cbStack, ['a', 'b', 'c']);\n      wb.write('x', () => { cbStack.push('x'); });\n      wb.write('', () => {\n        assert.deepEqual(stack, ['a', 'b', 'c', 'x', '']);\n        assert.deepEqual(cbStack, ['a', 'b', 'c', 'x']);\n        done();\n      });\n    });\n    it('flushSync with no pending writes is a no-op', () => {\n      wb.flushSync();\n      assert.deepEqual(stack, []);\n      assert.deepEqual(cbStack, []);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/input/WriteBuffer.ts",
    "content": "\n/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Disposable } from 'common/Lifecycle';\nimport { Emitter } from 'common/Event';\n\ndeclare const setTimeout: (handler: () => void, timeout?: number) => void;\n\n/**\n * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.\n * Enable flow control to avoid this limit and make sure that your backend correctly\n * propagates this to the underlying pty. (see docs for further instructions)\n * Since this limit is meant as a safety parachute to prevent browser crashs,\n * it is set to a very high number. Typically xterm.js gets unresponsive with\n * a 100 times lower number (>500 kB).\n */\nconst DISCARD_WATERMARK = 50000000; // ~50 MB\n\n/**\n * The max number of ms to spend on writes before allowing the renderer to\n * catch up with a 0ms setTimeout. A value of < 33 to keep us close to\n * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS\n * depends on the time it takes for the renderer to draw the frame.\n */\nconst WRITE_TIMEOUT_MS = 12;\n\n/**\n * Threshold of max held chunks in the write buffer, that were already processed.\n * This is a tradeoff between extensive write buffer shifts (bad runtime) and high\n * memory consumption by data thats not used anymore.\n */\nconst WRITE_BUFFER_LENGTH_THRESHOLD = 50;\n\nexport class WriteBuffer extends Disposable {\n  private _writeBuffer: (string | Uint8Array)[] = [];\n  private _callbacks: ((() => void) | undefined)[] = [];\n  private _pendingData = 0;\n  private _bufferOffset = 0;\n  private _isSyncWriting = false;\n  private _syncCalls = 0;\n  private _didUserInput = false;\n\n  private readonly _onWriteParsed = this._register(new Emitter<void>());\n  public readonly onWriteParsed = this._onWriteParsed.event;\n\n  constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) {\n    super();\n  }\n\n  public handleUserInput(): void {\n    this._didUserInput = true;\n  }\n\n  /**\n   * Flushes all pending writes synchronously. This is useful when you need to\n   * ensure all queued data is processed before performing an operation that\n   * depends upon everything being parsed like resize.\n   *\n   * Note: This is unreliable with async parser handlers as it does not wait for\n   * promises to resolve.\n   */\n  public flushSync(): void {\n    // exit early if another sync write loop is active\n    if (this._isSyncWriting) {\n      return;\n    }\n    this._isSyncWriting = true;\n\n    // Process all pending chunks synchronously\n    let chunk: string | Uint8Array | undefined;\n    while (chunk = this._writeBuffer.shift()) {\n      this._action(chunk);\n      const cb = this._callbacks.shift();\n      if (cb) cb();\n    }\n\n    // Reset buffer state\n    this._pendingData = 0;\n    this._bufferOffset = 0x7FFFFFFF;\n    this._writeBuffer.length = 0;\n    this._callbacks.length = 0;\n\n    this._isSyncWriting = false;\n  }\n\n  /**\n   * @deprecated Unreliable, to be removed soon.\n   */\n  public writeSync(data: string | Uint8Array, maxSubsequentCalls?: number): void {\n    // stop writeSync recursions with maxSubsequentCalls argument\n    // This is dangerous to use as it will lose the current data chunk\n    // and return immediately.\n    if (maxSubsequentCalls !== undefined && this._syncCalls > maxSubsequentCalls) {\n      // comment next line if a whole loop block should only contain x `writeSync` calls\n      // (total flat vs. deep nested limit)\n      this._syncCalls = 0;\n      return;\n    }\n    // append chunk to buffer\n    this._pendingData += data.length;\n    this._writeBuffer.push(data);\n    this._callbacks.push(undefined);\n\n    // increase recursion counter\n    this._syncCalls++;\n    // exit early if another writeSync loop is active\n    if (this._isSyncWriting) {\n      return;\n    }\n    this._isSyncWriting = true;\n\n    // force sync processing on pending data chunks to avoid in-band data scrambling\n    // does the same as innerWrite but without event loop\n    // we have to do it here as single loop steps to not corrupt loop subject\n    // by another writeSync call triggered from _action\n    let chunk: string | Uint8Array | undefined;\n    while (chunk = this._writeBuffer.shift()) {\n      this._action(chunk);\n      const cb = this._callbacks.shift();\n      if (cb) cb();\n    }\n    // reset to avoid reprocessing of chunks with scheduled innerWrite call\n    // stopping scheduled innerWrite by offset > length condition\n    this._pendingData = 0;\n    this._bufferOffset = 0x7FFFFFFF;\n\n    // allow another writeSync to loop\n    this._isSyncWriting = false;\n    this._syncCalls = 0;\n  }\n\n  public write(data: string | Uint8Array, callback?: () => void): void {\n    if (this._pendingData > DISCARD_WATERMARK) {\n      throw new Error('write data discarded, use flow control to avoid losing data');\n    }\n\n    // schedule chunk processing for next event loop run\n    if (!this._writeBuffer.length) {\n      this._bufferOffset = 0;\n\n      // If this is the first write call after the user has done some input,\n      // parse it immediately to minimize input latency,\n      // otherwise schedule for the next event\n      if (this._didUserInput) {\n        this._didUserInput = false;\n        this._pendingData += data.length;\n        this._writeBuffer.push(data);\n        this._callbacks.push(callback);\n        this._innerWrite();\n        return;\n      }\n\n      setTimeout(() => this._innerWrite());\n    }\n\n    this._pendingData += data.length;\n    this._writeBuffer.push(data);\n    this._callbacks.push(callback);\n  }\n\n  /**\n   * Inner write call, that enters the sliced chunk processing by timing.\n   *\n   * `lastTime` indicates, when the last _innerWrite call had started.\n   * It is used to aggregate async handler execution under a timeout constraint\n   * effectively lowering the redrawing needs, schematically:\n   *\n   *   macroTask _innerWrite:\n   *     if (performance.now() - (lastTime | 0) < WRITE_TIMEOUT_MS):\n   *        schedule microTask _innerWrite(lastTime)\n   *     else:\n   *        schedule macroTask _innerWrite(0)\n   *\n   *   overall execution order on task queues:\n   *\n   *   macrotasks:  [...]  -->  _innerWrite(0)  -->  [...]  -->  screenUpdate  -->  [...]\n   *         m  t:                    |\n   *         i  a:                  [...]\n   *         c  s:                    |\n   *         r  k:              while < timeout:\n   *         o  s:                _innerWrite(timeout)\n   *\n   * `promiseResult` depicts the promise resolve value of an async handler.\n   * This value gets carried forward through all saved stack states of the\n   * paused parser for proper continuation.\n   *\n   * Note, for pure sync code `lastTime` and `promiseResult` have no meaning.\n   */\n  protected _innerWrite(lastTime: number = 0, promiseResult: boolean = true): void {\n    const startTime = lastTime || performance.now();\n    while (this._writeBuffer.length > this._bufferOffset) {\n      const data = this._writeBuffer[this._bufferOffset];\n      const result = this._action(data, promiseResult);\n      if (result) {\n        /**\n         * If we get a promise as return value, we re-schedule the continuation\n         * as thenable on the promise and exit right away.\n         *\n         * The exit here means, that we block input processing at the current active chunk,\n         * the exact execution position within the chunk is preserved by the saved\n         * stack content in InputHandler and EscapeSequenceParser.\n         *\n         * Resuming happens automatically from that saved stack state.\n         * Also the resolved promise value is passed along the callstack to\n         * `EscapeSequenceParser.parse` to correctly resume the stopped handler loop.\n         *\n         * Exceptions on async handlers will be logged to console async, but do not interrupt\n         * the input processing (continues with next handler at the current input position).\n         */\n\n        /**\n         * If a promise takes long to resolve, we should schedule continuation behind setTimeout.\n         * This might already be too late, if our .then enters really late (executor + prev thens\n         * took very long). This cannot be solved here for the handler itself (it is the handlers\n         * responsibility to slice hard work), but we can at least schedule a screen update as we\n         * gain control.\n         */\n        const continuation: (r: boolean) => void = (r: boolean) => performance.now() - startTime >= WRITE_TIMEOUT_MS\n          ? setTimeout(() => this._innerWrite(0, r))\n          : this._innerWrite(startTime, r);\n\n        /**\n         * Optimization considerations:\n         * The continuation above favors FPS over throughput by eval'ing `startTime` on resolve.\n         * This might schedule too many screen updates with bad throughput drops (in case a slow\n         * resolving handler sliced its work properly behind setTimeout calls). We cannot spot\n         * this condition here, also the renderer has no way to spot nonsense updates either.\n         * FIXME: A proper fix for this would track the FPS at the renderer entry level separately.\n         *\n         * If favoring of FPS shows bad throughtput impact, use the following instead. It favors\n         * throughput by eval'ing `startTime` upfront pulling at least one more chunk into the\n         * current microtask queue (executed before setTimeout).\n         */\n        // const continuation: (r: boolean) => void = performance.now() - startTime >=\n        //     WRITE_TIMEOUT_MS\n        //   ? r => setTimeout(() => this._innerWrite(0, r))\n        //   : r => this._innerWrite(startTime, r);\n\n        // Handle exceptions synchronously to current band position, idea:\n        // 1. spawn a single microtask which we allow to throw hard\n        // 2. spawn a promise immediately resolving to `true`\n        // (executed on the same queue, thus properly aligned before continuation happens)\n        result.catch(err => {\n          queueMicrotask(() => {throw err;});\n          return Promise.resolve(false);\n        }).then(continuation);\n        return;\n      }\n\n      const cb = this._callbacks[this._bufferOffset];\n      if (cb) cb();\n      this._bufferOffset++;\n      this._pendingData -= data.length;\n\n      if (performance.now() - startTime >= WRITE_TIMEOUT_MS) {\n        break;\n      }\n    }\n    if (this._writeBuffer.length > this._bufferOffset) {\n      // Allow renderer to catch up before processing the next batch\n      // trim already processed chunks if we are above threshold\n      if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {\n        this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);\n        this._callbacks = this._callbacks.slice(this._bufferOffset);\n        this._bufferOffset = 0;\n      }\n      setTimeout(() => this._innerWrite());\n    } else {\n      this._writeBuffer.length = 0;\n      this._callbacks.length = 0;\n      this._pendingData = 0;\n      this._bufferOffset = 0;\n    }\n    this._onWriteParsed.fire();\n  }\n}\n"
  },
  {
    "path": "src/common/input/XParseColor.test.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { parseColor, toRgbString } from 'common/input/XParseColor';\n\ndescribe('XParseColor', () => {\n  describe('parseColor', () => {\n    it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {\n      // 4 bit\n      assert.deepEqual(parseColor('rgb:0/0/0'), [0, 0, 0]);\n      assert.deepEqual(parseColor('rgb:f/f/f'), [255, 255, 255]);\n      assert.deepEqual(parseColor('rgb:1/2/3'), [17, 34, 51]);\n      // 8 bit\n      assert.deepEqual(parseColor('rgb:00/00/00'), [0, 0, 0]);\n      assert.deepEqual(parseColor('rgb:ff/ff/ff'), [255, 255, 255]);\n      assert.deepEqual(parseColor('rgb:11/22/33'), [17, 34, 51]);\n      // 12 bit\n      assert.deepEqual(parseColor('rgb:000/000/000'), [0, 0, 0]);\n      assert.deepEqual(parseColor('rgb:fff/fff/fff'), [255, 255, 255]);\n      assert.deepEqual(parseColor('rgb:111/222/333'), [17, 34, 51]);\n      // 16 bit\n      assert.deepEqual(parseColor('rgb:0000/0000/0000'), [0, 0, 0]);\n      assert.deepEqual(parseColor('rgb:ffff/ffff/ffff'), [255, 255, 255]);\n      assert.deepEqual(parseColor('rgb:1111/2222/3333'), [17, 34, 51]);\n    });\n    it('#RGB scheme in 4/8/12/16 bit', () => {\n      // 4 bit\n      assert.deepEqual(parseColor('#000'), [0, 0, 0]);\n      assert.deepEqual(parseColor('#fff'), [240, 240, 240]);\n      assert.deepEqual(parseColor('#123'), [16, 32, 48]);\n      // 8 bit\n      assert.deepEqual(parseColor('#000000'), [0, 0, 0]);\n      assert.deepEqual(parseColor('#ffffff'), [255, 255, 255]);\n      assert.deepEqual(parseColor('#112233'), [17, 34, 51]);\n      // 12 bit\n      assert.deepEqual(parseColor('#000000000'), [0, 0, 0]);\n      assert.deepEqual(parseColor('#fffffffff'), [255, 255, 255]);\n      assert.deepEqual(parseColor('#111222333'), [17, 34, 51]);\n      // 16 bit\n      assert.deepEqual(parseColor('#000000000000'), [0, 0, 0]);\n      assert.deepEqual(parseColor('#ffffffffffff'), [255, 255, 255]);\n      assert.deepEqual(parseColor('#111122223333'), [17, 34, 51]);\n    });\n    it('supports upper case', () => {\n      assert.deepEqual(parseColor('RGB:0/A/F'), [0, 170, 255]);\n      assert.deepEqual(parseColor('#FFF'), [240, 240, 240]);\n    });\n    it('does not parse illegal combinations', () => {\n      // shifting bit width\n      assert.equal(parseColor('rgb:0/11/222'), undefined);\n      // unsupported scheme\n      assert.equal(parseColor('rgbi:00/11/22'), undefined);\n      // broken # specifier\n      assert.equal(parseColor('#aabbbcc'), undefined);\n      // out of range\n      assert.equal(parseColor('#aabbgg'), undefined);\n      assert.equal(parseColor('rgb:aa/bb/gg'), undefined);\n    });\n  });\n  describe('toXColorRgb', () => {\n    it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {\n      // 4 bit\n      assert.equal(toRgbString(parseColor('rgb:0/0/0')!, 4), 'rgb:0/0/0');\n      assert.equal(toRgbString(parseColor('rgb:f/f/f')!, 4), 'rgb:f/f/f');\n      assert.equal(toRgbString(parseColor('rgb:1/2/3')!, 4), 'rgb:1/2/3');\n      // 8 bit\n      assert.equal(toRgbString(parseColor('rgb:00/00/00')!, 8), 'rgb:00/00/00');\n      assert.equal(toRgbString(parseColor('rgb:ff/ff/ff')!, 8), 'rgb:ff/ff/ff');\n      assert.equal(toRgbString(parseColor('rgb:11/22/33')!, 8), 'rgb:11/22/33');\n      // 12 bit\n      assert.equal(toRgbString(parseColor('rgb:000/000/000')!, 12), 'rgb:000/000/000');\n      assert.equal(toRgbString(parseColor('rgb:fff/fff/fff')!, 12), 'rgb:fff/fff/fff');\n      assert.equal(toRgbString(parseColor('rgb:111/222/333')!, 12), 'rgb:111/222/333');\n      // 16 bit\n      assert.equal(toRgbString(parseColor('rgb:0000/0000/0000')!, 16), 'rgb:0000/0000/0000');\n      assert.equal(toRgbString(parseColor('rgb:ffff/ffff/ffff')!, 16), 'rgb:ffff/ffff/ffff');\n      assert.equal(toRgbString(parseColor('rgb:1111/2222/3333')!, 16), 'rgb:1111/2222/3333');\n    });\n    it('defaults to 16 bit output', () => {\n      assert.equal(toRgbString(parseColor('rgb:1/2/3')!), 'rgb:1111/2222/3333');\n      assert.equal(toRgbString(parseColor('rgb:11/22/33')!), 'rgb:1111/2222/3333');\n      assert.equal(toRgbString(parseColor('rgb:111/222/333')!), 'rgb:1111/2222/3333');\n      assert.equal(toRgbString(parseColor('rgb:123/123/123')!), 'rgb:1212/1212/1212');\n    });\n    it('reduces colors to 8 bit resolution', () => {\n      assert.equal(toRgbString(parseColor('rgb:123/123/123')!, 12), 'rgb:121/121/121');\n      assert.equal(toRgbString(parseColor('rgb:1234/1234/1234')!, 16), 'rgb:1212/1212/1212');\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/input/XParseColor.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n\n// 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits)\nconst RGB_REX = /^([\\da-f])\\/([\\da-f])\\/([\\da-f])$|^([\\da-f]{2})\\/([\\da-f]{2})\\/([\\da-f]{2})$|^([\\da-f]{3})\\/([\\da-f]{3})\\/([\\da-f]{3})$|^([\\da-f]{4})\\/([\\da-f]{4})\\/([\\da-f]{4})$/;\n// '#...' rule - matching any hex digits\nconst HASH_REX = /^[\\da-f]+$/;\n\n/**\n * Parse color spec to RGB values (8 bit per channel).\n * See `man xparsecolor` for details about certain format specifications.\n *\n * Supported formats:\n * - rgb:<red>/<green>/<blue> with <red>, <green>, <blue> in h | hh | hhh | hhhh\n * - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB\n *\n * All other formats like rgbi: or device-independent string specifications\n * with float numbering are not supported.\n */\nexport function parseColor(data: string): [number, number, number] | undefined {\n  if (!data) return;\n  // also handle uppercases\n  let low = data.toLowerCase();\n  if (low.startsWith('rgb:')) {\n    // 'rgb:' specifier\n    low = low.slice(4);\n    const m = RGB_REX.exec(low);\n    if (m) {\n      const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535;\n      return [\n        Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255),\n        Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255),\n        Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255)\n      ];\n    }\n  } else if (low.startsWith('#')) {\n    // '#' specifier\n    low = low.slice(1);\n    if (HASH_REX.exec(low) && [3, 6, 9, 12].includes(low.length)) {\n      const adv = low.length / 3;\n      const result: [number, number, number] = [0, 0, 0];\n      for (let i = 0; i < 3; ++i) {\n        const c = parseInt(low.slice(adv * i, adv * i + adv), 16);\n        result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8;\n      }\n      return result;\n    }\n  }\n\n  // Named colors are currently not supported due to the large addition to the xterm.js bundle size\n  // they would add. In order to support named colors, we would need some way of optionally loading\n  // additional payloads so startup/download time is not bloated (see #3530).\n}\n\n// pad hex output to requested bit width\nfunction pad(n: number, bits: number): string {\n  const s = n.toString(16);\n  const s2 = s.length < 2 ? '0' + s : s;\n  switch (bits) {\n    case 4:\n      return s[0];\n    case 8:\n      return s2;\n    case 12:\n      return (s2 + s2).slice(0, 3);\n    default:\n      return s2 + s2;\n  }\n}\n\n/**\n * Convert a given color to rgb:../../.. string of `bits` depth.\n */\nexport function toRgbString(color: [number, number, number], bits: number = 16): string {\n  const [r, g, b] = color;\n  return `rgb:${pad(r, bits)}/${pad(g, bits)}/${pad(b, bits)}`;\n}\n"
  },
  {
    "path": "src/common/parser/ApcParser.test.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { ApcParser, ApcHandler } from 'common/parser/ApcParser';\nimport { StringToUtf32, utf32ToString } from 'common/input/TextDecoder';\nimport { IApcHandler } from 'common/parser/Types';\n\nfunction toUtf32(s: string): Uint32Array {\n  const utf32 = new Uint32Array(s.length);\n  const decoder = new StringToUtf32();\n  const length = decoder.decode(s, utf32);\n  return utf32.subarray(0, length);\n}\n\nclass TestHandler implements IApcHandler {\n  public id: number;\n  public output: [string, number, string, (boolean | string)?][];\n  public msg: string;\n  public returnFalse: boolean;\n\n  constructor(\n    id: number,\n    output: [string, number, string, (boolean | string)?][],\n    msg: string,\n    returnFalse: boolean = false\n  ) {\n    this.id = id;\n    this.output = output;\n    this.msg = msg;\n    this.returnFalse = returnFalse;\n  }\n  public start(): void {\n    this.output.push([this.msg, this.id, 'START']);\n  }\n  public put(data: Uint32Array, start: number, end: number): void {\n    this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]);\n  }\n  public end(success: boolean): boolean {\n    this.output.push([this.msg, this.id, 'END', success]);\n    if (this.returnFalse) {\n      return false;\n    }\n    return true;\n  }\n}\n\ndescribe('ApcParser', () => {\n  let parser: ApcParser;\n  let reports: [number, string, (boolean | string | undefined)?][] = [];\n\n  beforeEach(() => {\n    reports = [];\n    parser = new ApcParser();\n    parser.setHandlerFallback((id: number, action: 'START' | 'PUT' | 'END', data?: string | boolean) => {\n      reports.push([id, action, data]);\n    });\n  });\n\n  describe('identifier parsing', () => {\n    it('single character identifier', () => {\n      parser.start();\n      const data = toUtf32('Gf=100,a=T;payload');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        [0x47, 'START', undefined],  // 0x47 = 'G'\n        [0x47, 'PUT', 'f=100,a=T;payload'],\n        [0x47, 'END', true]\n      ]);\n    });\n\n    it('identifier with no payload', () => {\n      parser.start();\n      const data = toUtf32('G');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        [0x47, 'START', undefined],\n        [0x47, 'END', true]\n      ]);\n    });\n\n    it('identifier with chunked payload', () => {\n      parser.start();\n      let data = toUtf32('Gf=100');\n      parser.put(data, 0, data.length);\n      data = toUtf32(',a=T');\n      parser.put(data, 0, data.length);\n      data = toUtf32(';payload');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        [0x47, 'START', undefined],\n        [0x47, 'PUT', 'f=100'],\n        [0x47, 'PUT', ',a=T'],\n        [0x47, 'PUT', ';payload'],\n        [0x47, 'END', true]\n      ]);\n    });\n\n    it('empty APC sequence', () => {\n      parser.start();\n      parser.end(true);\n      assert.deepEqual(reports, []);\n    });\n  });\n\n  describe('handler registration', () => {\n    let handlerReports: [string, number, string, (boolean | string)?][];\n\n    beforeEach(() => {\n      handlerReports = [];\n    });\n\n    it('registerHandler for specific identifier', () => {\n      const G_CODE = 0x47;  // 'G'\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'kitty'));\n      parser.start();\n      const data = toUtf32('Gf=100,a=T;imagedata');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(handlerReports, [\n        ['kitty', G_CODE, 'START'],\n        ['kitty', G_CODE, 'PUT', 'f=100,a=T;imagedata'],\n        ['kitty', G_CODE, 'END', true]\n      ]);\n      assert.deepEqual(reports, []);\n    });\n\n    it('unregistered identifier falls back', () => {\n      const G_CODE = 0x47;  // 'G'\n      const X_CODE = 0x58;  // 'X'\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'kitty'));\n      parser.start();\n      const data = toUtf32('Xsome data');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(handlerReports, []);\n      assert.deepEqual(reports, [\n        [X_CODE, 'START', undefined],\n        [X_CODE, 'PUT', 'some data'],\n        [X_CODE, 'END', true]\n      ]);\n    });\n\n    it('clearHandler removes handler', () => {\n      const G_CODE = 0x47;\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'kitty'));\n      parser.clearHandler(G_CODE);\n      parser.start();\n      const data = toUtf32('Gf=100');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(handlerReports, []);\n      assert.deepEqual(reports, [\n        [G_CODE, 'START', undefined],\n        [G_CODE, 'PUT', 'f=100'],\n        [G_CODE, 'END', true]\n      ]);\n    });\n\n    it('multiple handlers for same identifier', () => {\n      const G_CODE = 0x47;\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'handler1'));\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'handler2'));\n      parser.start();\n      const data = toUtf32('Gdata');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(handlerReports, [\n        ['handler2', G_CODE, 'START'],\n        ['handler1', G_CODE, 'START'],\n        ['handler2', G_CODE, 'PUT', 'data'],\n        ['handler1', G_CODE, 'PUT', 'data'],\n        ['handler2', G_CODE, 'END', true],\n        ['handler1', G_CODE, 'END', false]\n      ]);\n    });\n\n    it('handler returning false allows fallthrough', () => {\n      const G_CODE = 0x47;\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'handler1'));\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'handler2', true));\n      parser.start();\n      const data = toUtf32('Gdata');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(handlerReports, [\n        ['handler2', G_CODE, 'START'],\n        ['handler1', G_CODE, 'START'],\n        ['handler2', G_CODE, 'PUT', 'data'],\n        ['handler1', G_CODE, 'PUT', 'data'],\n        ['handler2', G_CODE, 'END', true],\n        ['handler1', G_CODE, 'END', true]\n      ]);\n    });\n\n    it('dispose removes handler', () => {\n      const G_CODE = 0x47;\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'handler1'));\n      const disposable = parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'handler2'));\n      disposable.dispose();\n      parser.start();\n      const data = toUtf32('Gdata');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(handlerReports, [\n        ['handler1', G_CODE, 'START'],\n        ['handler1', G_CODE, 'PUT', 'data'],\n        ['handler1', G_CODE, 'END', true]\n      ]);\n    });\n  });\n\n  describe('ApcHandler convenience class', () => {\n    const TEST_PAYLOAD_LIMIT = 100;\n    const CHUNK_SIZE = 10;\n    let originalPayloadLimit: number;\n\n    beforeEach(() => {\n      const handlerConstructor = ApcHandler as unknown as { _payloadLimit: number };\n      originalPayloadLimit = handlerConstructor._payloadLimit;\n      handlerConstructor._payloadLimit = TEST_PAYLOAD_LIMIT;\n    });\n\n    afterEach(() => {\n      const handlerConstructor = ApcHandler as unknown as { _payloadLimit: number };\n      handlerConstructor._payloadLimit = originalPayloadLimit;\n    });\n\n    it('should be called once on end(true)', () => {\n      const G_CODE = 0x47;\n      const results: [number, string][] = [];\n      parser.registerHandler(G_CODE, new ApcHandler((data: string) => {\n        results.push([G_CODE, data]);\n        return true;\n      }));\n      parser.start();\n      let data = toUtf32('Gf=100');\n      parser.put(data, 0, data.length);\n      data = toUtf32(',a=T;payload');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(results, [[G_CODE, 'f=100,a=T;payload']]);\n    });\n\n    it('should not be called on end(false)', () => {\n      const G_CODE = 0x47;\n      const results: [number, string][] = [];\n      parser.registerHandler(G_CODE, new ApcHandler((data: string) => {\n        results.push([G_CODE, data]);\n        return true;\n      }));\n      parser.start();\n      const data = toUtf32('Gf=100,a=T;payload');\n      parser.put(data, 0, data.length);\n      parser.end(false);\n      assert.deepEqual(results, []);\n    });\n\n    it('should handle payload up to limit', function(): void {\n      this.timeout(30000);\n      const G_CODE = 0x47;\n      const results: [number, string][] = [];\n      parser.registerHandler(G_CODE, new ApcHandler((data: string) => {\n        results.push([G_CODE, data]);\n        return true;\n      }));\n      parser.start();\n      let data = toUtf32('G');\n      parser.put(data, 0, data.length);\n      data = toUtf32('A'.repeat(CHUNK_SIZE));\n      for (let i = 0; i < TEST_PAYLOAD_LIMIT; i += CHUNK_SIZE) {\n        parser.put(data, 0, data.length);\n      }\n      parser.end(true);\n      assert.deepEqual(results, [[G_CODE, 'A'.repeat(TEST_PAYLOAD_LIMIT)]]);\n    });\n\n    it('should abort for payload over limit', function(): void {\n      this.timeout(30000);\n      const G_CODE = 0x47;\n      const results: [number, string][] = [];\n      parser.registerHandler(G_CODE, new ApcHandler((data: string) => {\n        results.push([G_CODE, data]);\n        return true;\n      }));\n      parser.start();\n      let data = toUtf32('G');\n      parser.put(data, 0, data.length);\n      data = toUtf32('A'.repeat(CHUNK_SIZE));\n      for (let i = 0; i < TEST_PAYLOAD_LIMIT; i += CHUNK_SIZE) {\n        parser.put(data, 0, data.length);\n      }\n      data = toUtf32('A');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(results, []);\n    });\n  });\n\n  describe('reset behavior', () => {\n    let handlerReports: [string, number, string, (boolean | string)?][];\n\n    beforeEach(() => {\n      handlerReports = [];\n    });\n\n    it('reset during payload cleans up handlers', () => {\n      const G_CODE = 0x47;\n      parser.registerHandler(G_CODE, new TestHandler(G_CODE, handlerReports, 'kitty'));\n      parser.start();\n      const data = toUtf32('Gf=100');\n      parser.put(data, 0, data.length);\n      parser.reset();\n      assert.deepEqual(handlerReports, [\n        ['kitty', G_CODE, 'START'],\n        ['kitty', G_CODE, 'PUT', 'f=100'],\n        ['kitty', G_CODE, 'END', false]\n      ]);\n    });\n  });\n});\n\ndescribe('ApcParser - async tests', () => {\n  let parser: ApcParser;\n  let reports: [number, string, (boolean | string | undefined)?][] = [];\n\n  beforeEach(() => {\n    reports = [];\n    parser = new ApcParser();\n    parser.setHandlerFallback((id: number, action: 'START' | 'PUT' | 'END', data?: string | boolean) => {\n      reports.push([id, action, data]);\n    });\n  });\n\n  async function endP(parser: ApcParser, success: boolean): Promise<void> {\n    let result: void | Promise<boolean>;\n    let prev: boolean | undefined;\n    while (result = parser.end(success, prev)) {\n      prev = await result;\n    }\n  }\n\n  describe('async ApcHandler', () => {\n    it('should handle async handler', async () => {\n      const G_CODE = 0x47;\n      const results: [number, string][] = [];\n      parser.registerHandler(G_CODE, new ApcHandler(async (data: string) => {\n        await new Promise(res => setTimeout(res, 10));\n        results.push([G_CODE, data]);\n        return true;\n      }));\n      parser.start();\n      const data = toUtf32('Gf=100,a=T');\n      parser.put(data, 0, data.length);\n      await endP(parser, true);\n      assert.deepEqual(results, [[G_CODE, 'f=100,a=T']]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/parser/ApcParser.ts",
    "content": "/**\n * Copyright (c) 2025 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IApcHandler, IHandlerCollection, ApcFallbackHandlerType, IApcParser, ISubParserStackState } from 'common/parser/Types';\nimport { ApcState, ParserConstants } from 'common/parser/Constants';\nimport { utf32ToString } from 'common/input/TextDecoder';\nimport { IDisposable } from 'common/Types';\n\nconst EMPTY_HANDLERS: IApcHandler[] = [];\n\n/**\n * APC Parser for handling Application Program Command sequences.\n * APC sequences use the format: ESC _ <identifier><data> ESC \\\n *\n * Unlike OSC which uses numeric identifiers (e.g., OSC 1337),\n * APC uses the first character as the identifier (e.g., 'G' for Kitty graphics).\n * The identifier is the character code of the first byte after ESC _.\n */\nexport class ApcParser implements IApcParser {\n  private _state = ApcState.START;\n  private _active = EMPTY_HANDLERS;\n  private _id = -1;\n  private _handlers: IHandlerCollection<IApcHandler> = Object.create(null);\n  private _handlerFb: ApcFallbackHandlerType = () => { };\n  private _stack: ISubParserStackState = {\n    paused: false,\n    loopPosition: 0,\n    fallThrough: false\n  };\n\n  /**\n   * Register an APC handler for a specific identifier.\n   * @param ident The character code of the first byte (e.g., 0x47 for 'G')\n   * @param handler The handler to register\n   */\n  public registerHandler(ident: number, handler: IApcHandler): IDisposable {\n    this._handlers[ident] ??= [];\n    const handlerList = this._handlers[ident];\n    handlerList.push(handler);\n    return {\n      dispose: () => {\n        const handlerIndex = handlerList.indexOf(handler);\n        if (handlerIndex !== -1) {\n          handlerList.splice(handlerIndex, 1);\n        }\n      }\n    };\n  }\n\n  public clearHandler(ident: number): void {\n    if (this._handlers[ident]) delete this._handlers[ident];\n  }\n\n  public setHandlerFallback(handler: ApcFallbackHandlerType): void {\n    this._handlerFb = handler;\n  }\n\n  public dispose(): void {\n    this._handlers = Object.create(null);\n    this._handlerFb = () => { };\n    this._active = EMPTY_HANDLERS;\n  }\n\n  public reset(): void {\n    // force cleanup handlers if payload was already sent\n    if (this._state === ApcState.PAYLOAD) {\n      for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {\n        this._active[j].end(false);\n      }\n    }\n    this._stack.paused = false;\n    this._active = EMPTY_HANDLERS;\n    this._id = -1;\n    this._state = ApcState.START;\n  }\n\n  private _start(): void {\n    this._active = this._handlers[this._id] || EMPTY_HANDLERS;\n    if (!this._active.length) {\n      this._handlerFb(this._id, 'START');\n    } else {\n      for (let j = this._active.length - 1; j >= 0; j--) {\n        this._active[j].start();\n      }\n    }\n  }\n\n  private _put(data: Uint32Array, start: number, end: number): void {\n    if (!this._active.length) {\n      this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));\n    } else {\n      for (let j = this._active.length - 1; j >= 0; j--) {\n        this._active[j].put(data, start, end);\n      }\n    }\n  }\n\n  public start(): void {\n    // always reset leftover handlers\n    this.reset();\n    this._state = ApcState.ID;\n  }\n\n  /**\n   * Put data to current APC command.\n   * For APC, the first character is used as the identifier.\n   * Format: ESC _ <identifier><payload> ESC \\\n   * Example: ESC _ G f=100,a=T;... ESC \\ (Kitty graphics, identifier='G')\n   */\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._state === ApcState.ABORT) {\n      return;\n    }\n    if (this._state === ApcState.ID) {\n      // The first character is the identifier\n      if (start < end) {\n        this._id = data[start++];\n        this._state = ApcState.PAYLOAD;\n        this._start();\n      }\n    }\n    if (this._state === ApcState.PAYLOAD && end - start > 0) {\n      this._put(data, start, end);\n    }\n  }\n\n  /**\n   * Indicates end of an APC command.\n   * Whether the APC got aborted or finished normally\n   * is indicated by `success`.\n   */\n  public end(success: boolean, promiseResult: boolean = true): void | Promise<boolean> {\n    if (this._state === ApcState.START) {\n      return;\n    }\n    // do nothing if command was faulty\n    if (this._state !== ApcState.ABORT) {\n      // if we are still in ID state and get an early end\n      // means we got an empty APC sequence with no identifier,\n      // which is invalid - just reset and return\n      if (this._state === ApcState.ID) {\n        this._active = EMPTY_HANDLERS;\n        this._id = -1;\n        this._state = ApcState.START;\n        return;\n      }\n\n      if (!this._active.length) {\n        this._handlerFb(this._id, 'END', success);\n      } else {\n        let handlerResult: boolean | Promise<boolean> = false;\n        let j = this._active.length - 1;\n        let fallThrough = false;\n        if (this._stack.paused) {\n          j = this._stack.loopPosition - 1;\n          handlerResult = promiseResult;\n          fallThrough = this._stack.fallThrough;\n          this._stack.paused = false;\n        }\n        if (!fallThrough && handlerResult === false) {\n          for (; j >= 0; j--) {\n            handlerResult = this._active[j].end(success);\n            if (handlerResult === true) {\n              break;\n            } else if (handlerResult instanceof Promise) {\n              this._stack.paused = true;\n              this._stack.loopPosition = j;\n              this._stack.fallThrough = false;\n              return handlerResult;\n            }\n          }\n          j--;\n        }\n        // cleanup left over handlers\n        // we always have to call .end for proper cleanup,\n        // here we use `success` to indicate whether a handler should execute\n        for (; j >= 0; j--) {\n          handlerResult = this._active[j].end(false);\n          if (handlerResult instanceof Promise) {\n            this._stack.paused = true;\n            this._stack.loopPosition = j;\n            this._stack.fallThrough = true;\n            return handlerResult;\n          }\n        }\n      }\n\n    }\n    this._active = EMPTY_HANDLERS;\n    this._id = -1;\n    this._state = ApcState.START;\n  }\n}\n\n/**\n * Convenient class to allow attaching string based handler functions\n * as APC handlers.\n */\nexport class ApcHandler implements IApcHandler {\n  private static _payloadLimit = ParserConstants.PAYLOAD_LIMIT;\n\n  private _data = '';\n  private _hitLimit: boolean = false;\n\n  constructor(private _handler: (data: string) => boolean | Promise<boolean>) { }\n\n  public start(): void {\n    this._data = '';\n    this._hitLimit = false;\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._hitLimit) {\n      return;\n    }\n    this._data += utf32ToString(data, start, end);\n    if (this._data.length > ApcHandler._payloadLimit) {\n      this._data = '';\n      this._hitLimit = true;\n    }\n  }\n\n  public end(success: boolean): boolean | Promise<boolean> {\n    let ret: boolean | Promise<boolean> = false;\n    if (this._hitLimit) {\n      ret = false;\n    } else if (success) {\n      ret = this._handler(this._data);\n      if (ret instanceof Promise) {\n        // need to hold data until `ret` got resolved\n        // dont care for errors, data will be freed anyway on next start\n        return ret.then(res => {\n          this._data = '';\n          this._hitLimit = false;\n          return res;\n        });\n      }\n    }\n    this._data = '';\n    this._hitLimit = false;\n    return ret;\n  }\n}\n"
  },
  {
    "path": "src/common/parser/Constants.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * Internal states of EscapeSequenceParser.\n */\nexport const enum ParserState {\n  GROUND = 0,\n  ESCAPE = 1,\n  ESCAPE_INTERMEDIATE = 2,\n  CSI_ENTRY = 3,\n  CSI_PARAM = 4,\n  CSI_INTERMEDIATE = 5,\n  CSI_IGNORE = 6,\n  SOS_PM_STRING = 7,\n  OSC_STRING = 8,\n  DCS_ENTRY = 9,\n  DCS_PARAM = 10,\n  DCS_IGNORE = 11,\n  DCS_INTERMEDIATE = 12,\n  DCS_PASSTHROUGH = 13,\n  APC_STRING = 14,\n  // Number of states, meaning LAST_STATE + 1.\n  STATE_LENGTH = 15\n}\n\n/**\n * Internal actions of EscapeSequenceParser.\n */\nexport const enum ParserAction {\n  IGNORE = 0,\n  ERROR = 1,\n  PRINT = 2,\n  EXECUTE = 3,\n  OSC_START = 4,\n  OSC_PUT = 5,\n  OSC_END = 6,\n  CSI_DISPATCH = 7,\n  PARAM = 8,\n  COLLECT = 9,\n  ESC_DISPATCH = 10,\n  CLEAR = 11,\n  DCS_HOOK = 12,\n  DCS_PUT = 13,\n  DCS_UNHOOK = 14,\n  APC_START = 15,\n  APC_PUT = 16,\n  APC_END = 17\n}\n\n/**\n * Internal states of OscParser.\n */\nexport const enum OscState {\n  START = 0,\n  ID = 1,\n  PAYLOAD = 2,\n  ABORT = 3\n}\n\n/**\n * Internal states of ApcParser.\n */\nexport const enum ApcState {\n  START = 0,\n  ID = 1,\n  PAYLOAD = 2,\n  ABORT = 3\n}\n\n// payload limit for OSC and DCS\nexport const enum ParserConstants {\n  PAYLOAD_LIMIT = 10000000\n}\n"
  },
  {
    "path": "src/common/parser/DcsParser.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { DcsParser, DcsHandler } from 'common/parser/DcsParser';\nimport { IDcsHandler, IParams, IFunctionIdentifier } from 'common/parser/Types';\nimport { utf32ToString, StringToUtf32 } from 'common/input/TextDecoder';\nimport { Params } from 'common/parser/Params';\n\nfunction toUtf32(s: string): Uint32Array {\n  const utf32 = new Uint32Array(s.length);\n  const decoder = new StringToUtf32();\n  const length = decoder.decode(s, utf32);\n  return utf32.subarray(0, length);\n}\n\nfunction identifier(id: IFunctionIdentifier): number {\n  let res = 0;\n  if (id.prefix) {\n    if (id.prefix.length > 1) {\n      throw new Error('only one byte as prefix supported');\n    }\n    res = id.prefix.charCodeAt(0);\n    if (res && 0x3c > res || res > 0x3f) {\n      throw new Error('prefix must be in range 0x3c .. 0x3f');\n    }\n  }\n  if (id.intermediates) {\n    if (id.intermediates.length > 2) {\n      throw new Error('only two bytes as intermediates are supported');\n    }\n    for (let i = 0; i < id.intermediates.length; ++i) {\n      const intermediate = id.intermediates.charCodeAt(i);\n      if (0x20 > intermediate || intermediate > 0x2f) {\n        throw new Error('intermediate must be in range 0x20 .. 0x2f');\n      }\n      res <<= 8;\n      res |= intermediate;\n    }\n  }\n  if (id.final.length !== 1) {\n    throw new Error('final must be a single byte');\n  }\n  const finalCode = id.final.charCodeAt(0);\n  if (0x40 > finalCode || finalCode > 0x7e) {\n    throw new Error('final must be in range 0x40 .. 0x7e');\n  }\n  res <<= 8;\n  res |= finalCode;\n\n  return res;\n}\n\nclass TestHandler implements IDcsHandler {\n  constructor(public output: any[], public msg: string, public returnFalse: boolean = false) {}\n  public hook(params: IParams): void {\n    this.output.push([this.msg, 'HOOK', params.toArray()]);\n  }\n  public put(data: Uint32Array, start: number, end: number): void {\n    this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]);\n  }\n  public unhook(success: boolean): boolean {\n    this.output.push([this.msg, 'UNHOOK', success]);\n    if (this.returnFalse) {\n      return false;\n    }\n    return true;\n  }\n}\n\ndescribe('DcsParser', () => {\n  let parser: DcsParser;\n  let reports: any[] = [];\n  beforeEach(() => {\n    reports = [];\n    parser = new DcsParser();\n    parser.setHandlerFallback((id, action, data) => {\n      if (action === 'HOOK') {\n        data = data.toArray();\n      }\n      reports.push([id, action, data]);\n    });\n  });\n  describe('handler registration', () => {\n    it('setDcsHandler', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [\n        // messages from TestHandler\n        ['th', 'HOOK', [1, 2, 3]],\n        ['th', 'PUT', 'Here comes'],\n        ['th', 'PUT', 'the mouse!'],\n        ['th', 'UNHOOK', true]\n      ]);\n    });\n    it('clearDcsHandler', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));\n      parser.clearHandler(identifier({intermediates: '+', final: 'p'}));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [\n        // messages from fallback handler\n        [identifier({intermediates: '+', final: 'p'}), 'HOOK', [1, 2, 3]],\n        [identifier({intermediates: '+', final: 'p'}), 'PUT', 'Here comes'],\n        [identifier({intermediates: '+', final: 'p'}), 'PUT', 'the mouse!'],\n        [identifier({intermediates: '+', final: 'p'}), 'UNHOOK', true]\n      ]);\n    });\n    it('addDcsHandler', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2'));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [\n        ['th2', 'HOOK', [1, 2, 3]],\n        ['th1', 'HOOK', [1, 2, 3]],\n        ['th2', 'PUT', 'Here comes'],\n        ['th1', 'PUT', 'Here comes'],\n        ['th2', 'PUT', 'the mouse!'],\n        ['th1', 'PUT', 'the mouse!'],\n        ['th2', 'UNHOOK', true],\n        ['th1', 'UNHOOK', false]  // false due being already handled by th2!\n      ]);\n    });\n    it('addDcsHandler with return false', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [\n        ['th2', 'HOOK', [1, 2, 3]],\n        ['th1', 'HOOK', [1, 2, 3]],\n        ['th2', 'PUT', 'Here comes'],\n        ['th1', 'PUT', 'Here comes'],\n        ['th2', 'PUT', 'the mouse!'],\n        ['th1', 'PUT', 'the mouse!'],\n        ['th2', 'UNHOOK', true],\n        ['th1', 'UNHOOK', true]  // true since th2 indicated to keep bubbling\n      ]);\n    });\n    it('dispose handlers', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));\n      const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));\n      dispo.dispose();\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [\n        ['th1', 'HOOK', [1, 2, 3]],\n        ['th1', 'PUT', 'Here comes'],\n        ['th1', 'PUT', 'the mouse!'],\n        ['th1', 'UNHOOK', true]\n      ]);\n    });\n  });\n  describe('DcsHandlerFactory', () => {\n    const TEST_PAYLOAD_LIMIT = 100;\n    const CHUNK_SIZE = 10;\n    let originalPayloadLimit: number;\n\n    beforeEach(() => {\n      const handlerConstructor = DcsHandler as unknown as { _payloadLimit: number };\n      originalPayloadLimit = handlerConstructor._payloadLimit;\n      handlerConstructor._payloadLimit = TEST_PAYLOAD_LIMIT;\n    });\n\n    afterEach(() => {\n      const handlerConstructor = DcsHandler as unknown as { _payloadLimit: number };\n      handlerConstructor._payloadLimit = originalPayloadLimit;\n    });\n\n    it('should be called once on end(true)', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; }));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]);\n    });\n    it('should not be called on end(false)', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; }));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(false);\n      assert.deepEqual(reports, []);\n    });\n    it('should be disposable', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['one', params.toArray(), data]); return true; }));\n      const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return true; }));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!']]);\n      dispo.dispose();\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      data = toUtf32('some other');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' data');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]);\n    });\n    it('should respect return false', () => {\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['one', params.toArray(), data]); return true; }));\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; }));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'Here comes the mouse!']]);\n    });\n    it('should work up to payload limit', function(): void {\n      this.timeout(30000);\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; }));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      const data = toUtf32('A'.repeat(CHUNK_SIZE));\n      for (let i = 0; i < TEST_PAYLOAD_LIMIT; i += CHUNK_SIZE) {\n        parser.put(data, 0, data.length);\n      }\n      parser.unhook(true);\n      assert.deepEqual(reports, [[[1, 2, 3], 'A'.repeat(TEST_PAYLOAD_LIMIT)]]);\n    });\n    it('should abort for payload limit +1', function(): void {\n      this.timeout(30000);\n      parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; }));\n      parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n      let data = toUtf32('A'.repeat(CHUNK_SIZE));\n      for (let i = 0; i < TEST_PAYLOAD_LIMIT; i += CHUNK_SIZE) {\n        parser.put(data, 0, data.length);\n      }\n      data = toUtf32('A');\n      parser.put(data, 0, data.length);\n      parser.unhook(true);\n      assert.deepEqual(reports, []);\n    });\n  });\n});\n\n\nclass TestHandlerAsync implements IDcsHandler {\n  constructor(public output: any[], public msg: string, public returnFalse: boolean = false) {}\n  public hook(params: IParams): void {\n    this.output.push([this.msg, 'HOOK', params.toArray()]);\n  }\n  public put(data: Uint32Array, start: number, end: number): void {\n    this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]);\n  }\n  public async unhook(success: boolean): Promise<boolean> {\n    // simple sleep to check in tests whether ordering gets messed up\n    await Promise.resolve();\n    this.output.push([this.msg, 'UNHOOK', success]);\n    if (this.returnFalse) {\n      return false;\n    }\n    return true;\n  }\n}\nasync function unhookP(parser: DcsParser, success: boolean): Promise<void> {\n  let result: void | Promise<boolean>;\n  let prev: boolean | undefined;\n  while (result = parser.unhook(success, prev)) {\n    prev = await result;\n  }\n}\n\n\ndescribe('DcsParser - async tests', () => {\n  let parser: DcsParser;\n  let reports: any[] = [];\n  beforeEach(() => {\n    reports = [];\n    parser = new DcsParser();\n    parser.setHandlerFallback((id, action, data) => {\n      if (action === 'HOOK') {\n        data = data.toArray();\n      }\n      reports.push([id, action, data]);\n    });\n  });\n  describe('sync and async mixed', () => {\n    describe('sync | async | sync', () => {\n      it('first should run, cleanup action for others', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', false));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', false));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's2', false));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['s2', 'HOOK', [1, 2, 3]],\n          ['a1', 'HOOK', [1, 2, 3]],\n          ['s1', 'HOOK', [1, 2, 3]],\n          ['s2', 'PUT', 'Here comes'],\n          ['a1', 'PUT', 'Here comes'],\n          ['s1', 'PUT', 'Here comes'],\n          ['s2', 'PUT', 'the mouse!'],\n          ['a1', 'PUT', 'the mouse!'],\n          ['s1', 'PUT', 'the mouse!'],\n          ['s2', 'UNHOOK', true],\n          ['a1', 'UNHOOK', false],  // important: a1 before s1\n          ['s1', 'UNHOOK', false]\n        ]);\n      });\n      it('all should run', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', true));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', true));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's2', true));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['s2', 'HOOK', [1, 2, 3]],\n          ['a1', 'HOOK', [1, 2, 3]],\n          ['s1', 'HOOK', [1, 2, 3]],\n          ['s2', 'PUT', 'Here comes'],\n          ['a1', 'PUT', 'Here comes'],\n          ['s1', 'PUT', 'Here comes'],\n          ['s2', 'PUT', 'the mouse!'],\n          ['a1', 'PUT', 'the mouse!'],\n          ['s1', 'PUT', 'the mouse!'],\n          ['s2', 'UNHOOK', true],\n          ['a1', 'UNHOOK', true],  // important: a1 before s1\n          ['s1', 'UNHOOK', true]\n        ]);\n      });\n    });\n    describe('async | sync | async', () => {\n      it('first should run, cleanup action for others', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', false));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', false));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a2', false));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['a2', 'HOOK', [1, 2, 3]],\n          ['s1', 'HOOK', [1, 2, 3]],\n          ['a1', 'HOOK', [1, 2, 3]],\n          ['a2', 'PUT', 'Here comes'],\n          ['s1', 'PUT', 'Here comes'],\n          ['a1', 'PUT', 'Here comes'],\n          ['a2', 'PUT', 'the mouse!'],\n          ['s1', 'PUT', 'the mouse!'],\n          ['a1', 'PUT', 'the mouse!'],\n          ['a2', 'UNHOOK', true],\n          ['s1', 'UNHOOK', false],  // important: s1 between a2 .. a1\n          ['a1', 'UNHOOK', false]\n        ]);\n      });\n      it('all should run', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', true));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', true));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a2', true));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['a2', 'HOOK', [1, 2, 3]],\n          ['s1', 'HOOK', [1, 2, 3]],\n          ['a1', 'HOOK', [1, 2, 3]],\n          ['a2', 'PUT', 'Here comes'],\n          ['s1', 'PUT', 'Here comes'],\n          ['a1', 'PUT', 'Here comes'],\n          ['a2', 'PUT', 'the mouse!'],\n          ['s1', 'PUT', 'the mouse!'],\n          ['a1', 'PUT', 'the mouse!'],\n          ['a2', 'UNHOOK', true],\n          ['s1', 'UNHOOK', true],  // important: s1 between a2 .. a1\n          ['a1', 'UNHOOK', true]\n        ]);\n      });\n    });\n    describe('DcsHandlerFactory', () => {\n      it('should be called once on end(true)', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push([params.toArray(), data]); return true; }));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]);\n      });\n      it('should not be called on end(false)', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push([params.toArray(), data]); return true; }));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, false);\n        assert.deepEqual(reports, []);\n      });\n      it('should be disposable', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['one', params.toArray(), data]); return true; }));\n        const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['two', params.toArray(), data]); return true; }));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!']]);\n        dispo.dispose();\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        data = toUtf32('some other');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' data');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]);\n      });\n      it('should respect return false', async () => {\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['one', params.toArray(), data]); return true; }));\n        parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['two', params.toArray(), data]); return false; }));\n        parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));\n        let data = toUtf32('Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await unhookP(parser, true);\n        assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'Here comes the mouse!']]);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/parser/DcsParser.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from 'common/Types';\nimport { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType, ISubParserStackState } from 'common/parser/Types';\nimport { utf32ToString } from 'common/input/TextDecoder';\nimport { Params } from 'common/parser/Params';\nimport { ParserConstants } from 'common/parser/Constants';\n\nconst EMPTY_HANDLERS: IDcsHandler[] = [];\n\nexport class DcsParser implements IDcsParser {\n  private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);\n  private _active: IDcsHandler[] = EMPTY_HANDLERS;\n  private _ident: number = 0;\n  private _handlerFb: DcsFallbackHandlerType = () => { };\n  private _stack: ISubParserStackState = {\n    paused: false,\n    loopPosition: 0,\n    fallThrough: false\n  };\n\n  public dispose(): void {\n    this._handlers = Object.create(null);\n    this._handlerFb = () => { };\n    this._active = EMPTY_HANDLERS;\n  }\n\n  public registerHandler(ident: number, handler: IDcsHandler): IDisposable {\n    this._handlers[ident] ??= [];\n    const handlerList = this._handlers[ident];\n    handlerList.push(handler);\n    return {\n      dispose: () => {\n        const handlerIndex = handlerList.indexOf(handler);\n        if (handlerIndex !== -1) {\n          handlerList.splice(handlerIndex, 1);\n        }\n      }\n    };\n  }\n\n  public clearHandler(ident: number): void {\n    if (this._handlers[ident]) delete this._handlers[ident];\n  }\n\n  public setHandlerFallback(handler: DcsFallbackHandlerType): void {\n    this._handlerFb = handler;\n  }\n\n  public reset(): void {\n    // force cleanup leftover handlers\n    if (this._active.length) {\n      for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {\n        this._active[j].unhook(false);\n      }\n    }\n    this._stack.paused = false;\n    this._active = EMPTY_HANDLERS;\n    this._ident = 0;\n  }\n\n  public hook(ident: number, params: IParams): void {\n    // always reset leftover handlers\n    this.reset();\n    this._ident = ident;\n    this._active = this._handlers[ident] || EMPTY_HANDLERS;\n    if (!this._active.length) {\n      this._handlerFb(this._ident, 'HOOK', params);\n    } else {\n      for (let j = this._active.length - 1; j >= 0; j--) {\n        this._active[j].hook(params);\n      }\n    }\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (!this._active.length) {\n      this._handlerFb(this._ident, 'PUT', utf32ToString(data, start, end));\n    } else {\n      for (let j = this._active.length - 1; j >= 0; j--) {\n        this._active[j].put(data, start, end);\n      }\n    }\n  }\n\n  public unhook(success: boolean, promiseResult: boolean = true): void | Promise<boolean> {\n    if (!this._active.length) {\n      this._handlerFb(this._ident, 'UNHOOK', success);\n    } else {\n      let handlerResult: boolean | Promise<boolean> = false;\n      let j = this._active.length - 1;\n      let fallThrough = false;\n      if (this._stack.paused) {\n        j = this._stack.loopPosition - 1;\n        handlerResult = promiseResult;\n        fallThrough = this._stack.fallThrough;\n        this._stack.paused = false;\n      }\n      if (!fallThrough && handlerResult === false) {\n        for (; j >= 0; j--) {\n          handlerResult = this._active[j].unhook(success);\n          if (handlerResult === true) {\n            break;\n          } else if (handlerResult instanceof Promise) {\n            this._stack.paused = true;\n            this._stack.loopPosition = j;\n            this._stack.fallThrough = false;\n            return handlerResult;\n          }\n        }\n        j--;\n      }\n      // cleanup left over handlers (fallThrough for async)\n      for (; j >= 0; j--) {\n        handlerResult = this._active[j].unhook(false);\n        if (handlerResult instanceof Promise) {\n          this._stack.paused = true;\n          this._stack.loopPosition = j;\n          this._stack.fallThrough = true;\n          return handlerResult;\n        }\n      }\n    }\n    this._active = EMPTY_HANDLERS;\n    this._ident = 0;\n  }\n}\n\n// predefine empty params as [0] (ZDM)\nconst EMPTY_PARAMS = new Params();\nEMPTY_PARAMS.addParam(0);\n\n/**\n * Convenient class to create a DCS handler from a single callback function.\n * Note: The payload is currently limited to 50 MB (hardcoded).\n */\nexport class DcsHandler implements IDcsHandler {\n  private static _payloadLimit = ParserConstants.PAYLOAD_LIMIT;\n\n  private _data = '';\n  private _params: IParams = EMPTY_PARAMS;\n  private _hitLimit: boolean = false;\n\n  constructor(private _handler: (data: string, params: IParams) => boolean | Promise<boolean>) { }\n\n  public hook(params: IParams): void {\n    // since we need to preserve params until `unhook`, we have to clone it\n    // (only borrowed from parser and spans multiple parser states)\n    // perf optimization:\n    // clone only, if we have non empty params, otherwise stick with default\n    this._params = (params.length > 1 || params.params[0]) ? params.clone() : EMPTY_PARAMS;\n    this._data = '';\n    this._hitLimit = false;\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._hitLimit) {\n      return;\n    }\n    this._data += utf32ToString(data, start, end);\n    if (this._data.length > DcsHandler._payloadLimit) {\n      this._data = '';\n      this._hitLimit = true;\n    }\n  }\n\n  public unhook(success: boolean): boolean | Promise<boolean> {\n    let ret: boolean | Promise<boolean> = false;\n    if (this._hitLimit) {\n      ret = false;\n    } else if (success) {\n      ret = this._handler(this._data, this._params);\n      if (ret instanceof Promise) {\n        // need to hold data and params until `ret` got resolved\n        // dont care for errors, data will be freed anyway on next start\n        return ret.then(res => {\n          this._params = EMPTY_PARAMS;\n          this._data = '';\n          this._hitLimit = false;\n          return res;\n        });\n      }\n    }\n    this._params = EMPTY_PARAMS;\n    this._data = '';\n    this._hitLimit = false;\n    return ret;\n  }\n}\n"
  },
  {
    "path": "src/common/parser/EscapeSequenceParser.test.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType, IFunctionIdentifier, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types';\nimport { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';\nimport { assert } from 'chai';\nimport { StringToUtf32, stringFromCodePoint, utf32ToString } from 'common/input/TextDecoder';\nimport { ParserState } from 'common/parser/Constants';\nimport { Params } from 'common/parser/Params';\nimport { OscHandler } from 'common/parser/OscParser';\nimport { IDisposable } from 'common/Types';\nimport { DcsHandler } from 'common/parser/DcsParser';\n\n\nfunction r(a: number, b: number): string[] {\n  let c = b - a;\n  const arr = new Array(c);\n  while (c--) {\n    arr[c] = String.fromCharCode(--b);\n  }\n  return arr;\n}\n\nclass MockOscPutParser implements IOscParser {\n  private _fallback: OscFallbackHandlerType = () => { };\n  public data = '';\n  public reset(): void {\n    this.data = '';\n  }\n  public put(data: Uint32Array, start: number, end: number): void {\n    this.data += utf32ToString(data, start, end);\n  }\n  public dispose(): void { }\n  public start(): void { }\n  public end(success: boolean): void {\n    this.data += `, success: ${success}`;\n    const id = parseInt(this.data.slice(0, this.data.indexOf(';')));\n    if (!isNaN(id)) {\n      this._fallback(id, 'END', this.data.slice(this.data.indexOf(';') + 1));\n    }\n  }\n  public registerHandler(ident: number, handler: IOscHandler): IDisposable {\n    throw new Error('not implemented');\n  }\n  public setHandler(ident: number, handler: IOscHandler): void {\n    throw new Error('not implemented');\n  }\n  public clearHandler(ident: number): void {\n    throw new Error('not implemented');\n  }\n  public setHandlerFallback(handler: OscFallbackHandlerType): void {\n    this._fallback = handler;\n  }\n}\nconst oscPutParser = new MockOscPutParser();\n\n// derived parser with access to internal states\nclass TestEscapeSequenceParser extends EscapeSequenceParser {\n  public get transitions(): TransitionTable {\n    return this._transitions;\n  }\n  public get osc(): string {\n    return (this._oscParser as MockOscPutParser).data;\n  }\n  public set osc(value: string) {\n    (this._oscParser as MockOscPutParser).data = value;\n  }\n  public get params(): ParamsArray {\n    return this._params.toArray();\n  }\n  public set params(value: ParamsArray) {\n    this._params = Params.fromArray(value);\n  }\n  public get realParams(): IParams {\n    return this._params;\n  }\n  public get collect(): string {\n    return this.identToString(this._collect);\n  }\n  public set collect(value: string) {\n    this._collect = 0;\n    for (let i = 0; i < value.length; ++i) {\n      this._collect <<= 8;\n      this._collect |= value.charCodeAt(i);\n    }\n  }\n  public mockOscParser(): void {\n    (this as any)._oscParser = oscPutParser;\n  }\n  public identifier(id: IFunctionIdentifier): number {\n    return this._identifier(id);\n  }\n  public get parseStack(): IParserStackState {\n    return this._parseStack;\n  }\n  private _trackStack = false;\n  public trackStackSavesOnPause(): void {\n    this._trackStack = true;\n  }\n  public trackedStack: IParserStackState[] = [];\n  public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean> {\n    const result = super.parse(data, length, promiseResult);\n    if (result instanceof Promise && this._trackStack) {\n      this.trackedStack.push({ ...this.parseStack });\n    }\n    return result;\n  }\n}\n\n// test object to collect parser actions and compare them with expected values\nconst testTerminal: any = {\n  calls: [],\n  clear(): void {\n    this.calls = [];\n  },\n  compare(value: any): void {\n    assert.deepEqual(this.calls, value);\n  },\n  print(data: Uint32Array, start: number, end: number): void {\n    let s = '';\n    for (let i = start; i < end; ++i) {\n      s += stringFromCodePoint(data[i]);\n    }\n    this.calls.push(['print', s]);\n  },\n  actionOSC(s: string): void {\n    this.calls.push(['osc', s]);\n  },\n  actionExecute(flag: string): void {\n    this.calls.push(['exe', flag]);\n  },\n  actionCSI(collect: string, params: IParams, flag: string): void {\n    this.calls.push(['csi', collect, params.toArray(), flag]);\n  },\n  actionESC(collect: string, flag: string): void {\n    this.calls.push(['esc', collect, flag]);\n  },\n  actionDCSHook(params: IParams): void {\n    this.calls.push(['dcs hook', params.toArray()]);\n  },\n  actionDCSPrint(s: string): void {\n    this.calls.push(['dcs put', s]);\n  },\n  actionDCSUnhook(success: boolean): void {\n    this.calls.push(['dcs unhook', success]);\n  }\n};\n\nconst states: number[] = [\n  ParserState.GROUND,\n  ParserState.ESCAPE,\n  ParserState.ESCAPE_INTERMEDIATE,\n  ParserState.CSI_ENTRY,\n  ParserState.CSI_PARAM,\n  ParserState.CSI_INTERMEDIATE,\n  ParserState.CSI_IGNORE,\n  ParserState.SOS_PM_STRING,\n  ParserState.OSC_STRING,\n  ParserState.DCS_ENTRY,\n  ParserState.DCS_PARAM,\n  ParserState.DCS_IGNORE,\n  ParserState.DCS_INTERMEDIATE,\n  ParserState.DCS_PASSTHROUGH,\n  ParserState.APC_STRING\n];\nlet state: any;\n\n// parser with Uint8Array based transition table\nconst testParser = new TestEscapeSequenceParser();\ntestParser.mockOscParser();\ntestParser.setPrintHandler(testTerminal.print.bind(testTerminal));\ntestParser.setCsiHandlerFallback((ident: number, params: IParams) => {\n  const id = testParser.identToString(ident);\n  testTerminal.actionCSI(id.slice(0, -1), params, id.slice(-1));\n});\ntestParser.setEscHandlerFallback((ident: number) => {\n  const id = testParser.identToString(ident);\n  testTerminal.actionESC(id.slice(0, -1), id.slice(-1));\n});\ntestParser.setExecuteHandlerFallback((code: number) => {\n  testTerminal.actionExecute(String.fromCharCode(code));\n});\ntestParser.setOscHandlerFallback((identifier, action, data) => {\n  if (identifier === -1) testTerminal.actionOSC(data);  // handle error condition silently\n  else if (action === 'END') testTerminal.actionOSC('' + identifier + ';' + data); // collect only data at END\n});\ntestParser.setDcsHandlerFallback((collectAndFlag, action, payload) => {\n  switch (action) {\n    case 'HOOK':\n      testTerminal.actionDCSHook(payload);\n      break;\n    case 'PUT':\n      testTerminal.actionDCSPrint(payload);\n      break;\n    case 'UNHOOK':\n      testTerminal.actionDCSUnhook(payload);\n  }\n});\n\n\n// translate string based parse calls into typed array based\nfunction parse(parser: TestEscapeSequenceParser, data: string): void {\n  const container = new Uint32Array(data.length);\n  const decoder = new StringToUtf32();\n  parser.parse(container, decoder.decode(data, container));\n}\n\ndescribe('EscapeSequenceParser', () => {\n  const parser = testParser;\n  describe('Parser init and methods', () => {\n    it('constructor', () => {\n      let p = new TestEscapeSequenceParser();\n      assert.deepEqual(p.transitions, VT500_TRANSITION_TABLE);\n      p = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE);\n      assert.deepEqual(p.transitions, VT500_TRANSITION_TABLE);\n      const tansitions: TransitionTable = new TransitionTable(10);\n      p = new TestEscapeSequenceParser(tansitions);\n      assert.deepEqual(p.transitions, tansitions);\n    });\n    it('inital states', () => {\n      assert.equal(parser.initialState, ParserState.GROUND);\n      assert.equal(parser.currentState, ParserState.GROUND);\n      assert.equal(parser.osc, '');\n      assert.deepEqual(parser.params, [0]);\n      assert.equal(parser.collect, '');\n    });\n    it('reset states', () => {\n      parser.currentState = 124;\n      parser.osc = '#';\n      parser.params = [123];\n      parser.collect = '#';\n\n      parser.reset();\n      assert.equal(parser.currentState, ParserState.GROUND);\n      assert.equal(parser.osc, '');\n      assert.deepEqual(parser.params, [0]);\n      assert.equal(parser.collect, '');\n    });\n  });\n  describe('state transitions and actions', () => {\n    it('state GROUND execute action', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.GROUND;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state GROUND print action', () => {\n      parser.reset();\n      testTerminal.clear();\n      const printables = r(0x20, 0x7f); // NOTE: DEL excluded\n      for (let i = 0; i < printables.length; ++i) {\n        parser.currentState = ParserState.GROUND;\n        parse(parser, printables[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([['print', printables[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans ANYWHERE --> GROUND with actions', () => {\n      const exes = [\n        '\\x18', '\\x1a',\n        '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', '\\x88',\n        '\\x89', '\\x8a', '\\x8b', '\\x8c', '\\x8d', '\\x8e', '\\x8f',\n        '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', '\\x99', '\\x9a'\n      ];\n      const exceptions: { [key: number]: { [key: string]: any[] } } = {\n        8: { '\\x18': [], '\\x1a': [] }, // abort OSC_STRING\n        13: { '\\x18': [['dcs unhook', false]], '\\x1a': [['dcs unhook', false]] }, // abort DCS_PASSTHROUGH\n        14: { '\\x18': [], '\\x1a': [] } // abort APC_STRING\n      };\n      parser.reset();\n      testTerminal.clear();\n      for (state in states) {\n        for (let i = 0; i < exes.length; ++i) {\n          parser.currentState = state;\n          parse(parser, exes[i]);\n          assert.equal(parser.currentState, ParserState.GROUND);\n          testTerminal.compare((state in exceptions ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]);\n          parser.reset();\n          testTerminal.clear();\n        }\n        parse(parser, '\\x9c');\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans ANYWHERE --> ESCAPE with clear', () => {\n      parser.reset();\n      for (state in states) {\n        parser.currentState = state;\n        parser.params = [23];\n        parser.collect = '#';\n        parse(parser, '\\x1b');\n        assert.equal(parser.currentState, ParserState.ESCAPE);\n        assert.deepEqual(parser.params, [0]);\n        assert.equal(parser.collect, '');\n        parser.reset();\n      }\n    });\n    it('state ESCAPE execute rules', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.ESCAPE;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.ESCAPE);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state ESCAPE ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.ESCAPE;\n      parse(parser, '\\x7f');\n      assert.equal(parser.currentState, ParserState.ESCAPE);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('trans ESCAPE --> GROUND with ecs_dispatch action', () => {\n      parser.reset();\n      testTerminal.clear();\n      let dispatches = r(0x30, 0x50);\n      dispatches = dispatches.concat(r(0x51, 0x58));\n      dispatches = dispatches.concat(['\\x59', '\\x5a']); // excluded \\x5c\n      dispatches = dispatches.concat(r(0x60, 0x7f));\n      for (let i = 0; i < dispatches.length; ++i) {\n        parser.currentState = ParserState.ESCAPE;\n        parse(parser, dispatches[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([['esc', '', dispatches[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans ESCAPE --> ESCAPE_INTERMEDIATE with collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.ESCAPE;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('state ESCAPE_INTERMEDIATE execute rules', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.ESCAPE_INTERMEDIATE;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state ESCAPE_INTERMEDIATE ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.ESCAPE_INTERMEDIATE;\n      parse(parser, '\\x7f');\n      assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('state ESCAPE_INTERMEDIATE collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.ESCAPE_INTERMEDIATE;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.ESCAPE_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('trans ESCAPE_INTERMEDIATE --> GROUND with esc_dispatch action', () => {\n      parser.reset();\n      testTerminal.clear();\n      const collect = r(0x30, 0x7f);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.ESCAPE_INTERMEDIATE;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        // '\\x5c' --> ESC + \\ (7bit ST) parser does not expose this as it already got handled\n        testTerminal.compare((collect[i] === '\\x5c') ? [] : [['esc', '', collect[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', () => {\n      parser.reset();\n      // C0\n      parser.currentState = ParserState.ESCAPE;\n      parser.params = [123];\n      parser.collect = '#';\n      parse(parser, '[');\n      assert.equal(parser.currentState, ParserState.CSI_ENTRY);\n      assert.deepEqual(parser.params, [0]);\n      assert.equal(parser.collect, '');\n      parser.reset();\n      // C1\n      for (state in states) {\n        parser.currentState = state;\n        parser.params = [123];\n        parser.collect = '#';\n        parse(parser, '\\x9b');\n        assert.equal(parser.currentState, ParserState.CSI_ENTRY);\n        assert.deepEqual(parser.params, [0]);\n        assert.equal(parser.collect, '');\n        parser.reset();\n      }\n    });\n    it('state CSI_ENTRY execute rules', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.CSI_ENTRY;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.CSI_ENTRY);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state CSI_ENTRY ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.CSI_ENTRY;\n      parse(parser, '\\x7f');\n      assert.equal(parser.currentState, ParserState.CSI_ENTRY);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('trans CSI_ENTRY --> GROUND with csi_dispatch action', () => {\n      parser.reset();\n      const dispatches = r(0x40, 0x7f);\n      for (let i = 0; i < dispatches.length; ++i) {\n        parser.currentState = ParserState.CSI_ENTRY;\n        parse(parser, dispatches[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([['csi', '', [0], dispatches[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans CSI_ENTRY --> CSI_PARAM with param/collect actions', () => {\n      parser.reset();\n      const params = ['\\x30', '\\x31', '\\x32', '\\x33', '\\x34', '\\x35', '\\x36', '\\x37', '\\x38', '\\x39'];\n      const collect = ['\\x3c', '\\x3d', '\\x3e', '\\x3f'];\n      for (let i = 0; i < params.length; ++i) {\n        parser.currentState = ParserState.CSI_ENTRY;\n        parse(parser, params[i]);\n        assert.equal(parser.currentState, ParserState.CSI_PARAM);\n        assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]);\n        parser.reset();\n      }\n      parser.currentState = ParserState.CSI_ENTRY;\n      parse(parser, '\\x3b');\n      assert.equal(parser.currentState, ParserState.CSI_PARAM);\n      assert.deepEqual(parser.params, [0, 0]);\n      parser.reset();\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.CSI_ENTRY;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.CSI_PARAM);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('state CSI_PARAM execute rules', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.CSI_PARAM;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.CSI_PARAM);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state CSI_PARAM param action', () => {\n      parser.reset();\n      const params = ['\\x30', '\\x31', '\\x32', '\\x33', '\\x34', '\\x35', '\\x36', '\\x37', '\\x38', '\\x39'];\n      for (let i = 0; i < params.length; ++i) {\n        parser.currentState = ParserState.CSI_PARAM;\n        parse(parser, params[i]);\n        assert.equal(parser.currentState, ParserState.CSI_PARAM);\n        assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]);\n        parser.reset();\n      }\n      parser.currentState = ParserState.CSI_PARAM;\n      parse(parser, '\\x3b');\n      assert.equal(parser.currentState, ParserState.CSI_PARAM);\n      assert.deepEqual(parser.params, [0, 0]);\n      parser.reset();\n    });\n    it('state CSI_PARAM ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.CSI_PARAM;\n      parse(parser, '\\x7f');\n      assert.equal(parser.currentState, ParserState.CSI_PARAM);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('trans CSI_PARAM --> GROUND with csi_dispatch action', () => {\n      parser.reset();\n      const dispatches = r(0x40, 0x7f);\n      for (let i = 0; i < dispatches.length; ++i) {\n        parser.currentState = ParserState.CSI_PARAM;\n        parser.params = [0, 1];\n        parse(parser, dispatches[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans CSI_ENTRY --> CSI_INTERMEDIATE with collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.CSI_ENTRY;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('trans CSI_PARAM --> CSI_INTERMEDIATE with collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.CSI_PARAM;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('state CSI_INTERMEDIATE execute rules', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.CSI_INTERMEDIATE;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state CSI_INTERMEDIATE collect', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.CSI_INTERMEDIATE;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('state CSI_INTERMEDIATE ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.CSI_INTERMEDIATE;\n      parse(parser, '\\x7f');\n      assert.equal(parser.currentState, ParserState.CSI_INTERMEDIATE);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('trans CSI_INTERMEDIATE --> GROUND with csi_dispatch action', () => {\n      parser.reset();\n      const dispatches = r(0x40, 0x7f);\n      for (let i = 0; i < dispatches.length; ++i) {\n        parser.currentState = ParserState.CSI_INTERMEDIATE;\n        parser.params = [0, 1];\n        parse(parser, dispatches[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans CSI_ENTRY --> CSI_PARAM for \":\" (0x3a)', () => {\n      parser.reset();\n      parser.currentState = ParserState.CSI_ENTRY;\n      parse(parser, '\\x3a');\n      assert.equal(parser.currentState, ParserState.CSI_PARAM);\n      parser.reset();\n    });\n    it('trans CSI_PARAM --> CSI_IGNORE', () => {\n      parser.reset();\n      const chars = ['\\x3c', '\\x3d', '\\x3e', '\\x3f'];\n      for (let i = 0; i < chars.length; ++i) {\n        parser.currentState = ParserState.CSI_PARAM;\n        parse(parser, '\\x3b' + chars[i]);\n        assert.equal(parser.currentState, ParserState.CSI_IGNORE);\n        assert.deepEqual(parser.params, [0, 0]);\n        parser.reset();\n      }\n    });\n    it('trans CSI_PARAM --> CSI_IGNORE', () => {\n      parser.reset();\n      const chars = ['\\x3c', '\\x3d', '\\x3e', '\\x3f'];\n      for (let i = 0; i < chars.length; ++i) {\n        assert.deepEqual(parser.params, [0]);\n        parser.currentState = ParserState.CSI_PARAM;\n        parse(parser, '\\x3b' + chars[i]);\n        assert.equal(parser.currentState, ParserState.CSI_IGNORE);\n        assert.deepEqual(parser.params, [0, 0]);\n        parser.reset();\n      }\n    });\n    it('trans CSI_INTERMEDIATE --> CSI_IGNORE', () => {\n      parser.reset();\n      const chars = r(0x30, 0x40);\n      for (let i = 0; i < chars.length; ++i) {\n        parser.currentState = ParserState.CSI_INTERMEDIATE;\n        parse(parser, chars[i]);\n        assert.equal(parser.currentState, ParserState.CSI_IGNORE);\n        assert.deepEqual(parser.params, [0]);\n        parser.reset();\n      }\n    });\n    it('state CSI_IGNORE execute rules', () => {\n      parser.reset();\n      testTerminal.clear();\n      let exes = r(0x00, 0x18);\n      exes = exes.concat(['\\x19']);\n      exes = exes.concat(r(0x1c, 0x20));\n      for (let i = 0; i < exes.length; ++i) {\n        parser.currentState = ParserState.CSI_IGNORE;\n        parse(parser, exes[i]);\n        assert.equal(parser.currentState, ParserState.CSI_IGNORE);\n        testTerminal.compare([['exe', exes[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state CSI_IGNORE ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      let ignored = r(0x20, 0x40);\n      ignored = ignored.concat(['\\x7f']);\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.CSI_IGNORE;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.CSI_IGNORE);\n        testTerminal.compare([]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans CSI_IGNORE --> GROUND', () => {\n      parser.reset();\n      const dispatches = r(0x40, 0x7f);\n      for (let i = 0; i < dispatches.length; ++i) {\n        parser.currentState = ParserState.CSI_IGNORE;\n        parser.params = [0, 1];\n        parse(parser, dispatches[i]);\n        assert.equal(parser.currentState, ParserState.GROUND);\n        testTerminal.compare([]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans ANYWHERE/ESCAPE --> SOS_PM_STRING', () => {\n      parser.reset();\n      // C0 (only SOS and PM, APC has separate handling)\n      let initializers = ['\\x58', '\\x5e'];\n      for (let i = 0; i < initializers.length; ++i) {\n        parse(parser, '\\x1b' + initializers[i]);\n        assert.equal(parser.currentState, ParserState.SOS_PM_STRING);\n        parser.reset();\n      }\n      // C1 (only SOS and PM, APC has separate handling)\n      for (state in states) {\n        parser.currentState = state;\n        initializers = ['\\x98', '\\x9e'];\n        for (let i = 0; i < initializers.length; ++i) {\n          parse(parser, initializers[i]);\n          assert.equal(parser.currentState, ParserState.SOS_PM_STRING);\n          parser.reset();\n        }\n      }\n    });\n    it('trans ANYWHERE/ESCAPE --> APC_STRING', () => {\n      parser.reset();\n      // C0 (ESC _)\n      parse(parser, '\\x1b_');\n      assert.equal(parser.currentState, ParserState.APC_STRING);\n      parser.reset();\n      // C1\n      for (state in states) {\n        parser.currentState = state;\n        parse(parser, '\\x9f');\n        assert.equal(parser.currentState, ParserState.APC_STRING);\n        parser.reset();\n      }\n    });\n    it('state SOS_PM_STRING ignore rules', () => {\n      parser.reset();\n      let ignored = r(0x00, 0x18);\n      ignored = ignored.concat(['\\x19']);\n      ignored = ignored.concat(r(0x1c, 0x20));\n      ignored = ignored.concat(r(0x20, 0x80));\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.SOS_PM_STRING;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.SOS_PM_STRING);\n        parser.reset();\n      }\n    });\n    it('trans ANYWHERE/ESCAPE --> OSC_STRING', () => {\n      parser.reset();\n      // C0\n      parse(parser, '\\x1b]');\n      assert.equal(parser.currentState, ParserState.OSC_STRING);\n      parser.reset();\n      // C1\n      for (state in states) {\n        parser.currentState = state;\n        parse(parser, '\\x9d');\n        assert.equal(parser.currentState, ParserState.OSC_STRING);\n        parser.reset();\n      }\n    });\n    it('state OSC_STRING ignore rules', () => {\n      parser.reset();\n      const ignored = [\n        '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', /* '\\x07', */ '\\x08',\n        '\\x09', '\\x0a', '\\x0b', '\\x0c', '\\x0d', '\\x0e', '\\x0f', '\\x10', '\\x11',\n        '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x19', '\\x1c', '\\x1d', '\\x1e', '\\x1f'];\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.OSC_STRING;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.OSC_STRING);\n        assert.equal(parser.osc, '');\n        parser.reset();\n      }\n    });\n    it('state OSC_STRING put action', () => {\n      parser.reset();\n      const puts = r(0x20, 0x80);\n      for (let i = 0; i < puts.length; ++i) {\n        parser.currentState = ParserState.OSC_STRING;\n        parse(parser, puts[i]);\n        assert.equal(parser.currentState, ParserState.OSC_STRING);\n        assert.equal(parser.osc, puts[i]);\n        parser.reset();\n      }\n    });\n    it('state DCS_ENTRY', () => {\n      parser.reset();\n      // C0\n      parse(parser, '\\x1bP');\n      assert.equal(parser.currentState, ParserState.DCS_ENTRY);\n      parser.reset();\n      // C1\n      for (state in states) {\n        parser.currentState = state;\n        parse(parser, '\\x90');\n        assert.equal(parser.currentState, ParserState.DCS_ENTRY);\n        parser.reset();\n      }\n    });\n    it('state DCS_ENTRY ignore rules', () => {\n      parser.reset();\n      const ignored = [\n        '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', '\\x08',\n        '\\x09', '\\x0a', '\\x0b', '\\x0c', '\\x0d', '\\x0e', '\\x0f', '\\x10', '\\x11',\n        '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x19', '\\x1c', '\\x1d', '\\x1e', '\\x1f', '\\x7f'];\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.DCS_ENTRY;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.DCS_ENTRY);\n        parser.reset();\n      }\n    });\n    it('state DCS_ENTRY --> DCS_PARAM with param/collect actions', () => {\n      parser.reset();\n      const params = ['\\x30', '\\x31', '\\x32', '\\x33', '\\x34', '\\x35', '\\x36', '\\x37', '\\x38', '\\x39'];\n      const collect = ['\\x3c', '\\x3d', '\\x3e', '\\x3f'];\n      for (let i = 0; i < params.length; ++i) {\n        parser.currentState = ParserState.DCS_ENTRY;\n        parse(parser, params[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PARAM);\n        assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]);\n        parser.reset();\n      }\n      parser.currentState = ParserState.DCS_ENTRY;\n      parse(parser, '\\x3b');\n      assert.equal(parser.currentState, ParserState.DCS_PARAM);\n      assert.deepEqual(parser.params, [0, 0]);\n      parser.reset();\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_ENTRY;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PARAM);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('state DCS_PARAM ignore rules', () => {\n      parser.reset();\n      const ignored = [\n        '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', '\\x08',\n        '\\x09', '\\x0a', '\\x0b', '\\x0c', '\\x0d', '\\x0e', '\\x0f', '\\x10', '\\x11',\n        '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x19', '\\x1c', '\\x1d', '\\x1e', '\\x1f', '\\x7f'];\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.DCS_PARAM;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PARAM);\n        parser.reset();\n      }\n    });\n    it('state DCS_PARAM param action', () => {\n      parser.reset();\n      const params = ['\\x30', '\\x31', '\\x32', '\\x33', '\\x34', '\\x35', '\\x36', '\\x37', '\\x38', '\\x39'];\n      for (let i = 0; i < params.length; ++i) {\n        parser.currentState = ParserState.DCS_PARAM;\n        parse(parser, params[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PARAM);\n        assert.deepEqual(parser.params, [params[i].charCodeAt(0) - 48]);\n        parser.reset();\n      }\n      parser.currentState = ParserState.DCS_PARAM;\n      parse(parser, '\\x3b');\n      assert.equal(parser.currentState, ParserState.DCS_PARAM);\n      assert.deepEqual(parser.params, [0, 0]);\n      parser.reset();\n    });\n    it('trans DCS_ENTRY --> DCS_PARAM for \":\" (0x3a)', () => {\n      parser.reset();\n      parser.currentState = ParserState.DCS_ENTRY;\n      parse(parser, '\\x3a');\n      assert.equal(parser.currentState, ParserState.DCS_PARAM);\n      parser.reset();\n    });\n    it('trans DCS_PARAM --> DCS_IGNORE', () => {\n      parser.reset();\n      const chars = ['\\x3c', '\\x3d', '\\x3e', '\\x3f'];\n      for (let i = 0; i < chars.length; ++i) {\n        parser.currentState = ParserState.DCS_PARAM;\n        parse(parser, '\\x3b' + chars[i]);\n        assert.equal(parser.currentState, ParserState.DCS_IGNORE);\n        assert.deepEqual(parser.params, [0, 0]);\n        parser.reset();\n      }\n    });\n    it('trans DCS_INTERMEDIATE --> DCS_IGNORE', () => {\n      parser.reset();\n      const chars = r(0x30, 0x40);\n      for (let i = 0; i < chars.length; ++i) {\n        parser.currentState = ParserState.DCS_INTERMEDIATE;\n        parse(parser, chars[i]);\n        assert.equal(parser.currentState, ParserState.DCS_IGNORE);\n        parser.reset();\n      }\n    });\n    it('state DCS_IGNORE ignore rules', () => {\n      parser.reset();\n      let ignored = [\n        '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', '\\x08',\n        '\\x09', '\\x0a', '\\x0b', '\\x0c', '\\x0d', '\\x0e', '\\x0f', '\\x10', '\\x11',\n        '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x19', '\\x1c', '\\x1d', '\\x1e', '\\x1f', '\\x7f'];\n      ignored = ignored.concat(r(0x20, 0x80));\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.DCS_IGNORE;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.DCS_IGNORE);\n        parser.reset();\n      }\n    });\n    it('trans DCS_ENTRY --> DCS_INTERMEDIATE with collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_ENTRY;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('trans DCS_PARAM --> DCS_INTERMEDIATE with collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_PARAM;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('state DCS_INTERMEDIATE ignore rules', () => {\n      parser.reset();\n      const ignored = [\n        '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', '\\x08',\n        '\\x09', '\\x0a', '\\x0b', '\\x0c', '\\x0d', '\\x0e', '\\x0f', '\\x10', '\\x11',\n        '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x19', '\\x1c', '\\x1d', '\\x1e', '\\x1f', '\\x7f'];\n      for (let i = 0; i < ignored.length; ++i) {\n        parser.currentState = ParserState.DCS_INTERMEDIATE;\n        parse(parser, ignored[i]);\n        assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE);\n        parser.reset();\n      }\n    });\n    it('state DCS_INTERMEDIATE collect action', () => {\n      parser.reset();\n      const collect = r(0x20, 0x30);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_INTERMEDIATE;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_INTERMEDIATE);\n        assert.equal(parser.collect, collect[i]);\n        parser.reset();\n      }\n    });\n    it('trans DCS_INTERMEDIATE --> DCS_IGNORE', () => {\n      parser.reset();\n      const chars = r(0x30, 0x40);\n      for (let i = 0; i < chars.length; ++i) {\n        parser.currentState = ParserState.DCS_INTERMEDIATE;\n        parse(parser, '\\x20' + chars[i]);\n        assert.equal(parser.currentState, ParserState.DCS_IGNORE);\n        assert.equal(parser.collect, '\\x20');\n        parser.reset();\n      }\n    });\n    it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', () => {\n      parser.reset();\n      testTerminal.clear();\n      const collect = r(0x40, 0x7f);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_ENTRY;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH);\n        testTerminal.compare([['dcs hook', [0]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans DCS_PARAM --> DCS_PASSTHROUGH with hook', () => {\n      parser.reset();\n      testTerminal.clear();\n      const collect = r(0x40, 0x7f);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_PARAM;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH);\n        testTerminal.compare([['dcs hook', [0]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('trans DCS_INTERMEDIATE --> DCS_PASSTHROUGH with hook', () => {\n      parser.reset();\n      testTerminal.clear();\n      const collect = r(0x40, 0x7f);\n      for (let i = 0; i < collect.length; ++i) {\n        parser.currentState = ParserState.DCS_INTERMEDIATE;\n        parse(parser, collect[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH);\n        testTerminal.compare([['dcs hook', [0]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state DCS_PASSTHROUGH put action', () => {\n      parser.reset();\n      testTerminal.clear();\n      let puts = r(0x00, 0x18);\n      puts = puts.concat(['\\x19']);\n      puts = puts.concat(r(0x1c, 0x20));\n      puts = puts.concat(r(0x20, 0x7f));\n      for (let i = 0; i < puts.length; ++i) {\n        parser.currentState = ParserState.DCS_PASSTHROUGH;\n        parse(parser, puts[i]);\n        assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH);\n        testTerminal.compare([['dcs put', puts[i]]]);\n        parser.reset();\n        testTerminal.clear();\n      }\n    });\n    it('state DCS_PASSTHROUGH ignore', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.DCS_PASSTHROUGH;\n      parse(parser, '\\x7f');\n      assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n  });\n\n  function test(s: string, value: any, noReset: any): void {\n    if (!noReset) {\n      parser.reset();\n      testTerminal.clear();\n    }\n    parse(parser, s);\n    testTerminal.compare(value);\n  }\n\n  describe('escape sequence examples', () => {\n    it('CSI with print and execute', () => {\n      test('\\x1b[<31;5mHello World! öäü€\\nabc',\n        [\n          ['csi', '<', [31, 5], 'm'],\n          ['print', 'Hello World! öäü€'],\n          ['exe', '\\n'],\n          ['print', 'abc']\n        ], null);\n    });\n    it('OSC', () => {\n      test('\\x1b]0;abc123€öäü\\x07', [\n        ['osc', '0;abc123€öäü, success: true']\n      ], null);\n    });\n    it('single DCS', () => {\n      test('\\x1bP1;2;3+$aäbc;däe\\x9c', [\n        ['dcs hook', [1, 2, 3]],\n        ['dcs put', 'äbc;däe'],\n        ['dcs unhook', true]\n      ], null);\n    });\n    it('multi DCS', () => {\n      test('\\x1bP1;2;3+$abc;de', [\n        ['dcs hook', [1, 2, 3]],\n        ['dcs put', 'bc;de']\n      ], null);\n      testTerminal.clear();\n      test('abc\\x9c', [\n        ['dcs put', 'abc'],\n        ['dcs unhook', true]\n      ], true);\n    });\n    it('print + DCS(C1)', () => {\n      test('abc\\x901;2;3+$abc;de\\x9c', [\n        ['print', 'abc'],\n        ['dcs hook', [1, 2, 3]],\n        ['dcs put', 'bc;de'],\n        ['dcs unhook', true]\n      ], null);\n    });\n    it('print + PM(C1) + print', () => {\n      test('abc\\x98123tzf\\x9cdefg', [\n        ['print', 'abc'],\n        ['print', 'defg']\n      ], null);\n    });\n    it('print + OSC(C1) + print', () => {\n      test('abc\\x9d123;tzf\\x9cdefg', [\n        ['print', 'abc'],\n        ['osc', '123;tzf, success: true'],\n        ['print', 'defg']\n      ], null);\n    });\n    it('error recovery', () => {\n      test('\\x1b[1€abcdefg\\x9b<;c', [\n        ['print', 'abcdefg'],\n        ['csi', '<', [0, 0], 'c']\n      ], null);\n    });\n    it('7bit ST should be swallowed', () => {\n      test('abc\\x9d123;tzf\\x1b\\\\defg', [\n        ['print', 'abc'],\n        ['osc', '123;tzf, success: true'],\n        ['print', 'defg']\n      ], null);\n    });\n    it('colon notation in CSI params', () => {\n      test('\\x1b[<31;5::123:;8mHello World! öäü€\\nabc',\n        [\n          ['csi', '<', [31, 5, [-1, 123, -1], 8], 'm'],\n          ['print', 'Hello World! öäü€'],\n          ['exe', '\\n'],\n          ['print', 'abc']\n        ], null);\n    });\n    it('colon notation in DCS params', () => {\n      test('abc\\x901;2::55;3+$abc;de\\x9c', [\n        ['print', 'abc'],\n        ['dcs hook', [1, 2, [-1, 55], 3]],\n        ['dcs put', 'bc;de'],\n        ['dcs unhook', true]\n      ], null);\n    });\n    it('CAN should abort DCS', () => {\n      test('abc\\x901;2::55;3+$abc;de\\x18', [\n        ['print', 'abc'],\n        ['dcs hook', [1, 2, [-1, 55], 3]],\n        ['dcs put', 'bc;de'],\n        ['dcs unhook', false] // false for abort\n      ], null);\n    });\n    it('SUB should abort DCS', () => {\n      test('abc\\x901;2::55;3+$abc;de\\x1a', [\n        ['print', 'abc'],\n        ['dcs hook', [1, 2, [-1, 55], 3]],\n        ['dcs put', 'bc;de'],\n        ['dcs unhook', false] // false for abort\n      ], null);\n    });\n    it('CAN should abort OSC', () => {\n      test('\\x1b]0;abc123€öäü\\x18', [\n        ['osc', '0;abc123€öäü, success: false']\n      ], null);\n    });\n    it('SUB should abort OSC', () => {\n      test('\\x1b]0;abc123€öäü\\x1a', [\n        ['osc', '0;abc123€öäü, success: false']\n      ], null);\n    });\n  });\n\n  describe('coverage tests', () => {\n    it('CSI_IGNORE error', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.CSI_IGNORE;\n      parse(parser, '€öäü');\n      assert.equal(parser.currentState, ParserState.CSI_IGNORE);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('DCS_IGNORE error', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.DCS_IGNORE;\n      parse(parser, '€öäü');\n      assert.equal(parser.currentState, ParserState.DCS_IGNORE);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('DCS_PASSTHROUGH error', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.DCS_PASSTHROUGH;\n      parse(parser, '\\x901;2;3+$a€öäü');\n      assert.equal(parser.currentState, ParserState.DCS_PASSTHROUGH);\n      testTerminal.compare([['dcs hook', [1, 2, 3]], ['dcs put', '€öäü']]);\n      parser.reset();\n      testTerminal.clear();\n    });\n    it('error else of if (code > 159)', () => {\n      parser.reset();\n      testTerminal.clear();\n      parser.currentState = ParserState.GROUND;\n      parse(parser, '\\x9c');\n      assert.equal(parser.currentState, ParserState.GROUND);\n      testTerminal.compare([]);\n      parser.reset();\n      testTerminal.clear();\n    });\n  });\n\n  describe('set/clear handler', () => {\n    const INPUT = '\\x1b[1;31mhello \\x1b%Gwor\\x1bEld!\\x1b[0m\\r\\n$>\\x1b]1;foo=bar\\x1b\\\\';\n    let parser2: TestEscapeSequenceParser;\n    let print = '';\n    const esc: string[] = [];\n    const csi: [string, ParamsArray, string][] = [];\n    const exe: string[] = [];\n    const osc: [number, string][] = [];\n    const dcs: ([string] | [string, string] | [string, string, ParamsArray, number])[] = [];\n    function clearAccu(): void {\n      print = '';\n      esc.length = 0;\n      csi.length = 0;\n      exe.length = 0;\n      osc.length = 0;\n      dcs.length = 0;\n    }\n    beforeEach(() => {\n      parser2 = new TestEscapeSequenceParser();\n      clearAccu();\n    });\n    it('print handler', () => {\n      parser2.setPrintHandler(function (data: Uint32Array, start: number, end: number): void {\n        for (let i = start; i < end; ++i) {\n          print += stringFromCodePoint(data[i]);\n        }\n      });\n      parse(parser2, INPUT);\n      assert.equal(print, 'hello world!$>');\n      parser2.clearPrintHandler();\n      parser2.clearPrintHandler(); // should not throw\n      clearAccu();\n      parse(parser2, INPUT);\n      assert.equal(print, '');\n    });\n    it('ESC handler', () => {\n      parser2.registerEscHandler({ intermediates: '%', final: 'G' }, function (): boolean {\n        esc.push('%G');\n        return true;\n      });\n      parser2.registerEscHandler({ final: 'E' }, function (): boolean {\n        esc.push('E');\n        return true;\n      });\n      parse(parser2, INPUT);\n      assert.deepEqual(esc, ['%G', 'E']);\n      parser2.clearEscHandler({ intermediates: '%', final: 'G' });\n      parser2.clearEscHandler({ intermediates: '%', final: 'G' }); // should not throw\n      clearAccu();\n      parse(parser2, INPUT);\n      assert.deepEqual(esc, ['E']);\n      parser2.clearEscHandler({ final: 'E' });\n      clearAccu();\n      parse(parser2, INPUT);\n      assert.deepEqual(esc, []);\n    });\n    describe('ESC custom handlers', () => {\n      it('prevent fallback', () => {\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; });\n        parse(parser2, INPUT);\n        assert.deepEqual(esc, ['custom - %G']);\n      });\n      it('allow fallback', () => {\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return false; });\n        parse(parser2, INPUT);\n        assert.deepEqual(esc, ['custom - %G', 'default - %G']);\n      });\n      it('Multiple custom handlers fallback once', () => {\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom2 - %G'); return false; });\n        parse(parser2, INPUT);\n        assert.deepEqual(esc, ['custom2 - %G', 'custom - %G']);\n      });\n      it('Multiple custom handlers no fallback', () => {\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom2 - %G'); return true; });\n        parse(parser2, INPUT);\n        assert.deepEqual(esc, ['custom2 - %G']);\n      });\n      it('Execution order should go from latest handler down to the original', () => {\n        const order: number[] = [];\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { order.push(1); return true; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { order.push(2); return false; });\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { order.push(3); return false; });\n        parse(parser2, '\\x1b%G');\n        assert.deepEqual(order, [3, 2, 1]);\n      });\n      it('Dispose should work', () => {\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; });\n        const dispo = parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; });\n        dispo.dispose();\n        parse(parser2, INPUT);\n        assert.deepEqual(esc, ['default - %G']);\n      });\n      it('Should not corrupt the parser when dispose is called twice', () => {\n        parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('default - %G'); return true; });\n        const dispo = parser2.registerEscHandler({ intermediates: '%', final: 'G' }, () => { esc.push('custom - %G'); return true; });\n        dispo.dispose();\n        dispo.dispose();\n        parse(parser2, INPUT);\n        assert.deepEqual(esc, ['default - %G']);\n      });\n    });\n    it('CSI handler', () => {\n      parser2.registerCsiHandler({ final: 'm' }, function (params: IParams): boolean {\n        csi.push(['m', params.toArray(), '']);\n        return true;\n      });\n      parse(parser2, INPUT);\n      assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']]);\n      parser2.clearCsiHandler({ final: 'm' });\n      parser2.clearCsiHandler({ final: 'm' }); // should not throw\n      clearAccu();\n      parse(parser2, INPUT);\n      assert.deepEqual(csi, []);\n    });\n    describe('CSI custom handlers', () => {\n      it('Prevent fallback', () => {\n        const csiCustom: [string, ParamsArray, string][] = [];\n        parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; });\n        parse(parser2, INPUT);\n        assert.deepEqual(csi, [], 'Should not fallback to original handler');\n        assert.deepEqual(csiCustom, [['m', [1, 31], ''], ['m', [0], '']]);\n      });\n      it('Allow fallback', () => {\n        const csiCustom: [string, ParamsArray, string][] = [];\n        parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return false; });\n        parse(parser2, INPUT);\n        assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');\n        assert.deepEqual(csiCustom, [['m', [1, 31], ''], ['m', [0], '']]);\n      });\n      it('Multiple custom handlers fallback once', () => {\n        const csiCustom: [string, ParamsArray, string][] = [];\n        const csiCustom2: [string, ParamsArray, string][] = [];\n        parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom2.push(['m', params.toArray(), '']); return false; });\n        parse(parser2, INPUT);\n        assert.deepEqual(csi, [], 'Should not fallback to original handler');\n        assert.deepEqual(csiCustom, [['m', [1, 31], ''], ['m', [0], '']]);\n        assert.deepEqual(csiCustom2, [['m', [1, 31], ''], ['m', [0], '']]);\n      });\n      it('Multiple custom handlers no fallback', () => {\n        const csiCustom: [string, ParamsArray, string][] = [];\n        const csiCustom2: [string, ParamsArray, string][] = [];\n        parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom2.push(['m', params.toArray(), '']); return true; });\n        parse(parser2, INPUT);\n        assert.deepEqual(csi, [], 'Should not fallback to original handler');\n        assert.deepEqual(csiCustom, [], 'Should not fallback once');\n        assert.deepEqual(csiCustom2, [['m', [1, 31], ''], ['m', [0], '']]);\n      });\n      it('Execution order should go from latest handler down to the original', () => {\n        const order: number[] = [];\n        parser2.registerCsiHandler({ final: 'm' }, () => { order.push(1); return true; });\n        parser2.registerCsiHandler({ final: 'm' }, () => { order.push(2); return false; });\n        parser2.registerCsiHandler({ final: 'm' }, () => { order.push(3); return false; });\n        parse(parser2, '\\x1b[0m');\n        assert.deepEqual(order, [3, 2, 1]);\n      });\n      it('Dispose should work', () => {\n        const csiCustom: [string, ParamsArray, string][] = [];\n        parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; });\n        const customHandler = parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; });\n        customHandler.dispose();\n        parse(parser2, INPUT);\n        assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']]);\n        assert.deepEqual(csiCustom, [], 'Should not use custom handler as it was disposed');\n      });\n      it('Should not corrupt the parser when dispose is called twice', () => {\n        const csiCustom: [string, ParamsArray, string][] = [];\n        parser2.registerCsiHandler({ final: 'm' }, params => { csi.push(['m', params.toArray(), '']); return true; });\n        const customHandler = parser2.registerCsiHandler({ final: 'm' }, params => { csiCustom.push(['m', params.toArray(), '']); return true; });\n        customHandler.dispose();\n        customHandler.dispose();\n        parse(parser2, INPUT);\n        assert.deepEqual(csi, [['m', [1, 31], ''], ['m', [0], '']]);\n        assert.deepEqual(csiCustom, [], 'Should not use custom handler as it was disposed');\n      });\n    });\n    it('EXECUTE handler', () => {\n      parser2.setExecuteHandler('\\n', function (): boolean {\n        exe.push('\\n');\n        return true;\n      });\n      parser2.setExecuteHandler('\\r', function (): boolean {\n        exe.push('\\r');\n        return true;\n      });\n      parse(parser2, INPUT);\n      assert.deepEqual(exe, ['\\r', '\\n']);\n      parser2.clearExecuteHandler('\\r');\n      parser2.clearExecuteHandler('\\r'); // should not throw\n      clearAccu();\n      parse(parser2, INPUT);\n      assert.deepEqual(exe, ['\\n']);\n    });\n    it('OSC handler', () => {\n      parser2.registerOscHandler(1, new OscHandler(function (data: string): boolean {\n        osc.push([1, data]);\n        return true;\n      }));\n      parse(parser2, INPUT);\n      assert.deepEqual(osc, [[1, 'foo=bar']]);\n      parser2.clearOscHandler(1);\n      parser2.clearOscHandler(1); // should not throw\n      clearAccu();\n      parse(parser2, INPUT);\n      assert.deepEqual(osc, []);\n    });\n    describe('OSC custom handlers', () => {\n      it('Prevent fallback', () => {\n        const oscCustom: [number, string][] = [];\n        parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));\n        parse(parser2, INPUT);\n        assert.deepEqual(osc, [], 'Should not fallback to original handler');\n        assert.deepEqual(oscCustom, [[1, 'foo=bar']]);\n      });\n      it('Allow fallback', () => {\n        const oscCustom: [number, string][] = [];\n        parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; }));\n        parse(parser2, INPUT);\n        assert.deepEqual(osc, [[1, 'foo=bar']], 'Should fallback to original handler');\n        assert.deepEqual(oscCustom, [[1, 'foo=bar']]);\n      });\n      it('Multiple custom handlers fallback once', () => {\n        const oscCustom: [number, string][] = [];\n        const oscCustom2: [number, string][] = [];\n        parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; }));\n        parse(parser2, INPUT);\n        assert.deepEqual(osc, [], 'Should not fallback to original handler');\n        assert.deepEqual(oscCustom, [[1, 'foo=bar']]);\n        assert.deepEqual(oscCustom2, [[1, 'foo=bar']]);\n      });\n      it('Multiple custom handlers no fallback', () => {\n        const oscCustom: [number, string][] = [];\n        const oscCustom2: [number, string][] = [];\n        parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; }));\n        parse(parser2, INPUT);\n        assert.deepEqual(osc, [], 'Should not fallback to original handler');\n        assert.deepEqual(oscCustom, [], 'Should not fallback once');\n        assert.deepEqual(oscCustom2, [[1, 'foo=bar']]);\n      });\n      it('Execution order should go from latest handler down to the original', () => {\n        const order: number[] = [];\n        parser2.registerOscHandler(1, new OscHandler(() => { order.push(1); return true; }));\n        parser2.registerOscHandler(1, new OscHandler(() => { order.push(2); return false; }));\n        parser2.registerOscHandler(1, new OscHandler(() => { order.push(3); return false; }));\n        parse(parser2, '\\x1b]1;foo=bar\\x1b\\\\');\n        assert.deepEqual(order, [3, 2, 1]);\n      });\n      it('Dispose should work', () => {\n        const oscCustom: [number, string][] = [];\n        parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; }));\n        const customHandler = parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));\n        customHandler.dispose();\n        parse(parser2, INPUT);\n        assert.deepEqual(osc, [[1, 'foo=bar']]);\n        assert.deepEqual(oscCustom, [], 'Should not use custom handler as it was disposed');\n      });\n      it('Should not corrupt the parser when dispose is called twice', () => {\n        const oscCustom: [number, string][] = [];\n        parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; }));\n        const customHandler = parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));\n        customHandler.dispose();\n        customHandler.dispose();\n        parse(parser2, INPUT);\n        assert.deepEqual(osc, [[1, 'foo=bar']]);\n        assert.deepEqual(oscCustom, [], 'Should not use custom handler as it was disposed');\n      });\n    });\n    it('DCS handler', () => {\n      parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, {\n        hook: function (params: IParams): void {\n          dcs.push(['hook', '', params.toArray(), 0]);\n        },\n        put: function (data: Uint32Array, start: number, end: number): void {\n          let s = '';\n          for (let i = start; i < end; ++i) {\n            s += stringFromCodePoint(data[i]);\n          }\n          dcs.push(['put', s]);\n        },\n        unhook: function (): boolean {\n          dcs.push(['unhook']);\n          return true;\n        }\n      });\n      parse(parser2, '\\x1bP1;2;3+pabc');\n      parse(parser2, ';de\\x9c');\n      assert.deepEqual(dcs, [\n        ['hook', '', [1, 2, 3], 0],\n        ['put', 'abc'], ['put', ';de'],\n        ['unhook']\n      ]);\n      parser2.clearDcsHandler({ intermediates: '+', final: 'p' });\n      parser2.clearDcsHandler({ intermediates: '+', final: 'p' }); // should not throw\n      clearAccu();\n      parse(parser2, '\\x1bP1;2;3+pabc');\n      parse(parser2, ';de\\x9c');\n      assert.deepEqual(dcs, []);\n    });\n    describe('DCS custom handlers', () => {\n      const DCS_INPUT = '\\x1bP1;2;3+pabc\\x1b\\\\';\n      it('Prevent fallback', () => {\n        const dcsCustom: [string, (number | number[])[], string][] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(dcsCustom, [['B', [1, 2, 3], 'abc']]);\n      });\n      it('Allow fallback', () => {\n        const dcsCustom: [string, (number | number[])[], string][] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; }));\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(dcsCustom, [['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]);\n      });\n      it('Multiple custom handlers fallback once', () => {\n        const dcsCustom: [string, (number | number[])[], string][] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; }));\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(dcsCustom, [['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]);\n      });\n      it('Multiple custom handlers no fallback', () => {\n        const dcsCustom: [string, (number | number[])[], string][] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; }));\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(dcsCustom, [['C', [1, 2, 3], 'abc']]);\n      });\n      it('Execution order should go from latest handler down to the original', () => {\n        const order: number[] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler(() => { order.push(1); return true; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler(() => { order.push(2); return false; }));\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler(() => { order.push(3); return false; }));\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(order, [3, 2, 1]);\n      });\n      it('Dispose should work', () => {\n        const dcsCustom: [string, (number | number[])[], string][] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; }));\n        const dispo = parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));\n        dispo.dispose();\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(dcsCustom, [['A', [1, 2, 3], 'abc']]);\n      });\n      it('Should not corrupt the parser when dispose is called twice', () => {\n        const dcsCustom: [string, (number | number[])[], string][] = [];\n        parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; }));\n        const dispo = parser2.registerDcsHandler({ intermediates: '+', final: 'p' }, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));\n        dispo.dispose();\n        dispo.dispose();\n        parse(parser2, DCS_INPUT);\n        assert.deepEqual(dcsCustom, [['A', [1, 2, 3], 'abc']]);\n      });\n    });\n    it('ERROR handler', () => {\n      let errorState: IParsingState | null = null;\n      parser2.setErrorHandler(function (state: IParsingState): IParsingState {\n        errorState = state;\n        return state;\n      });\n      parse(parser2, '\\x1b[1;2;€;3m'); // faulty escape sequence\n      assert.deepEqual(errorState, {\n        position: 6,\n        code: '€'.charCodeAt(0),\n        currentState: ParserState.CSI_PARAM,\n        collect: 0,\n        params: Params.fromArray([1, 2, 0]), // extra zero here\n        abort: false\n      });\n      parser2.clearErrorHandler();\n      parser2.clearErrorHandler(); // should not throw\n      errorState = null;\n      parse(parser2, '\\x1b[1;2;a;3m');\n      assert.equal(errorState, null);\n    });\n  });\n  describe('function identifiers', () => {\n    describe('registration limits', () => {\n      it('prefix range 0x3c .. 0x3f, one byte', () => {\n        for (let i = 0x3c; i <= 0x3f; ++i) {\n          const c = String.fromCharCode(i);\n          assert.equal(parser.identToString(parser.identifier({ prefix: c, final: 'z' })), c + 'z');\n        }\n        assert.throws(() => { parser.identifier({ prefix: '\\x3b', final: 'z' }); }, 'prefix must be in range 0x3c .. 0x3f');\n        assert.throws(() => { parser.identifier({ prefix: '\\x40', final: 'z' }); }, 'prefix must be in range 0x3c .. 0x3f');\n        assert.throws(() => { parser.identifier({ prefix: '??', final: 'z' }); }, 'only one byte as prefix supported');\n      });\n      it('intermediates range 0x20 .. 0x2f, up to two bytes', () => {\n        for (let i = 0x20; i <= 0x2f; ++i) {\n          const c = String.fromCharCode(i);\n          assert.equal(parser.identToString(parser.identifier({ intermediates: c + c, final: 'z' })), c + c + 'z');\n        }\n        assert.throws(() => { parser.identifier({ intermediates: '\\x1f', final: 'z' }); }, 'intermediate must be in range 0x20 .. 0x2f');\n        assert.throws(() => { parser.identifier({ intermediates: '\\x30', final: 'z' }); }, 'intermediate must be in range 0x20 .. 0x2f');\n        assert.throws(() => { parser.identifier({ intermediates: '!!!', final: 'z' }); }, 'only two bytes as intermediates are supported');\n      });\n      it('final CSI/DCS range 0x40 .. 0x7e (default), one byte', () => {\n        for (let i = 0x40; i <= 0x7e; ++i) {\n          const c = String.fromCharCode(i);\n          assert.equal(parser.identToString(parser.identifier({ final: c })), c);\n        }\n        assert.throws(() => { parser.identifier({ final: '\\x3f' }); }, 'final must be in range 64 .. 126');\n        assert.throws(() => { parser.identifier({ final: '\\x7f' }); }, 'final must be in range 64 .. 126');\n        assert.throws(() => { parser.identifier({ final: 'zz' }); }, 'final must be a single byte');\n      });\n      it('final ESC range 0x30 .. 0x7e, one byte', () => {\n        for (let i = 0x30; i <= 0x7e; ++i) {\n          const final = String.fromCharCode(i);\n          let handler: IDisposable | undefined;\n          assert.doesNotThrow(() => { handler = parser.registerEscHandler({ final }, () => true); }, 'final must be in range 48 .. 126');\n          if (handler) handler.dispose();\n        }\n        assert.throws(() => { parser.registerEscHandler({ final: '\\x2f' }, () => true); }, 'final must be in range 48 .. 126');\n        assert.throws(() => { parser.registerEscHandler({ final: '\\x7f' }, () => true); }, 'final must be in range 48 .. 126');\n      });\n      it('id calculation - should stacking prefix -> intermediate -> final', () => {\n        assert.equal(parser.identToString(parser.identifier({ final: 'z' })), 'z');\n        assert.equal(parser.identToString(parser.identifier({ prefix: '?', final: 'z' })), '?z');\n        assert.equal(parser.identToString(parser.identifier({ intermediates: '!', final: 'z' })), '!z');\n        assert.equal(parser.identToString(parser.identifier({ prefix: '?', intermediates: '!', final: 'z' })), '?!z');\n        assert.equal(parser.identToString(parser.identifier({ prefix: '?', intermediates: '!!', final: 'z' })), '?!!z');\n      });\n    });\n    describe('identifier invocation', () => {\n      it('ESC', () => {\n        const callstack: string[] = [];\n        const h1 = parser.registerEscHandler({ final: 'z' }, () => { callstack.push('z'); return true; });\n        const h2 = parser.registerEscHandler({ intermediates: '!', final: 'z' }, () => { callstack.push('!z'); return true; });\n        const h3 = parser.registerEscHandler({ intermediates: '!!', final: 'z' }, () => { callstack.push('!!z'); return true; });\n        parse(parser, '\\x1bz\\x1b!z\\x1b!!z');\n        h1.dispose();\n        h2.dispose();\n        h3.dispose();\n        parse(parser, '\\x1bz\\x1b!z\\x1b!!z');\n        assert.deepEqual(callstack, ['z', '!z', '!!z']);\n      });\n      it('CSI', () => {\n        const callstack: any[] = [];\n        const h1 = parser.registerCsiHandler({ final: 'z' }, params => { callstack.push(['z', params.toArray()]); return true; });\n        const h2 = parser.registerCsiHandler({ intermediates: '!', final: 'z' }, params => { callstack.push(['!z', params.toArray()]); return true; });\n        const h3 = parser.registerCsiHandler({ intermediates: '!!', final: 'z' }, params => { callstack.push(['!!z', params.toArray()]); return true; });\n        const h4 = parser.registerCsiHandler({ prefix: '?', final: 'z' }, params => { callstack.push(['?z', params.toArray()]); return true; });\n        const h5 = parser.registerCsiHandler({ prefix: '?', intermediates: '!', final: 'z' }, params => { callstack.push(['?!z', params.toArray()]); return true; });\n        const h6 = parser.registerCsiHandler({ prefix: '?', intermediates: '!!', final: 'z' }, params => { callstack.push(['?!!z', params.toArray()]); return true; });\n        parse(parser, '\\x1b[1;z\\x1b[1;!z\\x1b[1;!!z\\x1b[?1;z\\x1b[?1;!z\\x1b[?1;!!z');\n        h1.dispose();\n        h2.dispose();\n        h3.dispose();\n        h4.dispose();\n        h5.dispose();\n        h6.dispose();\n        parse(parser, '\\x1b[1;z\\x1b[1;!z\\x1b[1;!!z\\x1b[?1;z\\x1b[?1;!z\\x1b[?1;!!z');\n        assert.deepEqual(\n          callstack,\n          [['z', [1, 0]], ['!z', [1, 0]], ['!!z', [1, 0]], ['?z', [1, 0]], ['?!z', [1, 0]], ['?!!z', [1, 0]]]\n        );\n      });\n      it('DCS', () => {\n        const callstack: any[] = [];\n        const h1 = parser.registerDcsHandler({ final: 'z' }, new DcsHandler((data, params) => { callstack.push(['z', params.toArray(), data]); return true; }));\n        const h2 = parser.registerDcsHandler({ intermediates: '!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['!z', params.toArray(), data]); return true; }));\n        const h3 = parser.registerDcsHandler({ intermediates: '!!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['!!z', params.toArray(), data]); return true; }));\n        const h4 = parser.registerDcsHandler({ prefix: '?', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['?z', params.toArray(), data]); return true; }));\n        const h5 = parser.registerDcsHandler({ prefix: '?', intermediates: '!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['?!z', params.toArray(), data]); return true; }));\n        const h6 = parser.registerDcsHandler({ prefix: '?', intermediates: '!!', final: 'z' }, new DcsHandler((data, params) => { callstack.push(['?!!z', params.toArray(), data]); return true; }));\n        parse(parser, '\\x1bP1;zAB\\x1b\\\\\\x1bP1;!zAB\\x1b\\\\\\x1bP1;!!zAB\\x1b\\\\\\x1bP?1;zAB\\x1b\\\\\\x1bP?1;!zAB\\x1b\\\\\\x1bP?1;!!zAB\\x1b\\\\');\n        h1.dispose();\n        h2.dispose();\n        h3.dispose();\n        h4.dispose();\n        h5.dispose();\n        h6.dispose();\n        parse(parser, '\\x1bP1;zAB\\x1b\\\\\\x1bP1;!zAB\\x1b\\\\\\x1bP1;!!zAB\\x1b\\\\\\x1bP?1;zAB\\x1b\\\\\\x1bP?1;!zAB\\x1b\\\\\\x1bP?1;!!zAB\\x1b\\\\');\n        assert.deepEqual(\n          callstack,\n          [\n            ['z', [1, 0], 'AB'],\n            ['!z', [1, 0], 'AB'],\n            ['!!z', [1, 0], 'AB'],\n            ['?z', [1, 0], 'AB'],\n            ['?!z', [1, 0], 'AB'],\n            ['?!!z', [1, 0], 'AB']\n          ]\n        );\n      });\n    });\n  });\n  // TODO: error conditions and error recovery (not implemented yet in parser)\n});\n\n\n/**\n * async handler tests.\n */\n\nfunction parseSync(parser: TestEscapeSequenceParser, data: string): void | Promise<boolean> {\n  const container = new Uint32Array(data.length);\n  const decoder = new StringToUtf32();\n  return parser.parse(container, decoder.decode(data, container));\n}\nasync function parseP(parser: TestEscapeSequenceParser, data: string): Promise<void> {\n  const container = new Uint32Array(data.length);\n  const decoder = new StringToUtf32();\n  const len = decoder.decode(data, container);\n  let result: void | Promise<boolean>;\n  let prev: boolean | undefined;\n  while (result = parser.parse(container, len, prev)) {\n    prev = await result;\n  }\n}\nfunction evalStackSaves(stackSaves: IParserStackState[], data: [number, ParserStackType, number][]): void {\n  assert.equal(stackSaves.length, data.length);\n  for (let i = 0; i < data.length; ++i) {\n    assert.equal(stackSaves[i].chunkPos, data[i][0]);\n    assert.equal(stackSaves[i].state, data[i][1]);\n    assert.equal(stackSaves[i].handlerPos, data[i][2]);\n  }\n}\n// helper similiar to assert.throws for async functions\nasync function throwsAsync(fn: () => Promise<any>, message?: string | undefined): Promise<void> {\n  let msg: string | undefined;\n  try {\n    await fn();\n  } catch (e) {\n    if (e instanceof Error) {\n      msg = e.message;\n    } else if (typeof e === 'string') {\n      msg = e;\n    }\n    if (typeof message === 'string') {\n      assert.equal(msg, message);\n    }\n    return;\n  }\n  assert.throws(fn, message);\n}\n\ndescribe('EscapeSequenceParser - async', () => {\n  // sequences: SGR 1;31 | hello SP | ESC %G | wor | ESC E | ld! | SGR 0 | EXE \\r\\n | $> | DCS 1;2 a [xyz] ST | OSC 1;foo=bar ST | FIN\n  // needed handlers: CSI m, PRINT, ESC %G, ESC E, EXE \\r, EXE \\n, OSC 1\n  const INPUT = '\\x1b[1;31mhello \\x1b%Gwor\\x1bEld!\\x1b[0m\\r\\n$>\\x1bP1;2axyz\\x1b\\\\\\x1b]1;foo=bar\\x1b\\\\FIN';\n  let RESULT: any[];\n  let parser: TestEscapeSequenceParser;\n  const callstack: any[] = [];\n  function clearAccu(): void {\n    callstack.length = 0;\n    parser.trackedStack.length = 0;\n  }\n  beforeEach(() => {\n    RESULT = [\n      ['SGR', [1, 31]],\n      ['PRINT', 'hello '],\n      ['ESC %G'],\n      ['PRINT', 'wor'],\n      ['ESC E'],\n      ['PRINT', 'ld!'],\n      ['SGR', [0]],\n      ['EXE \\r'],\n      ['EXE \\n'],\n      ['PRINT', '$>'],\n      ['DCS a', ['xyz', [1, 2]]],\n      ['OSC 1', 'foo=bar'],\n      ['PRINT', 'FIN']\n    ];\n    parser = new TestEscapeSequenceParser();\n    parser.reset();\n    parser.trackStackSavesOnPause();\n    clearAccu();\n  });\n  describe('sync handlers should behave as before', () => {\n    beforeEach(() => {\n      parser.setPrintHandler((data, start, end) => {\n        let result = '';\n        for (let i = start; i < end; ++i) {\n          result += stringFromCodePoint(data[i]);\n        }\n        callstack.push(['PRINT', result]);\n      });\n      parser.registerCsiHandler({ final: 'm' }, params => { callstack.push(['SGR', params.toArray()]); return true; });\n      parser.registerEscHandler({ intermediates: '%', final: 'G' }, () => { callstack.push(['ESC %G']); return true; });\n      parser.registerEscHandler({ final: 'E' }, () => { callstack.push(['ESC E']); return true; });\n      parser.setExecuteHandler('\\r', () => { callstack.push(['EXE \\r']); return true; });\n      parser.setExecuteHandler('\\n', () => { callstack.push(['EXE \\n']); return true; });\n      parser.registerOscHandler(1, new OscHandler(data => { callstack.push(['OSC 1', data]); return true; }));\n      parser.registerDcsHandler({ final: 'a' }, new DcsHandler((data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true; }));\n    });\n\n    it('sync handlers keep being parsed in sync mode', () => {\n      // note: if we have only sync handlers, a parse call should never return anything\n      assert.equal(!parseSync(parser, INPUT), true);\n      assert.equal(parser.parseStack.state, ParserStackType.NONE);  // not paused\n      assert.equal(parser.trackedStack.length, 0);                  // never got paused\n    });\n    it('correct result on sync parse call', () => {\n      parseSync(parser, INPUT);\n      assert.deepEqual(callstack, RESULT);\n      assert.equal(parser.trackedStack.length, 0);\n    });\n    it('correct result on async parse call', async () => {\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT);\n      assert.equal(parser.trackedStack.length, 0);\n    });\n  });\n  describe('async handlers', () => {\n    beforeEach(() => {\n      parser.setPrintHandler((data, start, end) => {\n        let result = '';\n        for (let i = start; i < end; ++i) {\n          result += stringFromCodePoint(data[i]);\n        }\n        callstack.push(['PRINT', result]);\n      });\n      parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['SGR', params.toArray()]); return true; });\n      parser.registerEscHandler({ intermediates: '%', final: 'G' }, async () => { callstack.push(['ESC %G']); return true; });\n      parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['ESC E']); return true; });\n      parser.setExecuteHandler('\\r', () => { callstack.push(['EXE \\r']); return true; });\n      parser.setExecuteHandler('\\n', () => { callstack.push(['EXE \\n']); return true; });\n      parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['OSC 1', data]); return true; }));\n      parser.registerDcsHandler({ final: 'a' }, new DcsHandler(async (data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true; }));\n    });\n\n    it('sync parse call does not work anymore', () => {\n      assert.notEqual(!parseSync(parser, INPUT), true);\n      assert.notDeepEqual(callstack, RESULT);\n      // due to sync calling we should save exactly one saved stack\n      // proper continuation is not possible anymore, as we lost the promise resolve value\n      assert.equal(parser.trackedStack.length, 1);\n    });\n    it('improper continuation should throw', async () => {\n      /**\n       * Explanation:\n       * The first sync call will stop at the first promise returned,\n       * but does not await its resolve value.\n       * The second sync call to parse will fail due to missing `promiseResult`,\n       * which is needed for correct continuation.\n       */\n      assert.notEqual(!parseSync(parser, INPUT), true);\n      assert.notDeepEqual(callstack, RESULT);\n      assert.throws(() => parseSync(parser, INPUT), 'improper continuation due to previous async handler, giving up parsing');\n      // keeps being broken for further parse calls (sync and async)\n      assert.throws(() => parseSync(parser, 'random'), 'improper continuation due to previous async handler, giving up parsing');\n      await throwsAsync(() => parseP(parser, 'foobar'), 'improper continuation due to previous async handler, giving up parsing');\n      // reset should lift the error condition\n      parser.reset();\n      await parseP(parser, INPUT); // does not throw anymore\n    });\n    it('correct result on awaited parse call', async () => {\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT);\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n    });\n    it('correct result on chunked awaited parse calls', async () => {\n      RESULT = [\n        ['SGR', [1, 31]],\n        ['PRINT', 'h'],  // due to single char input PRINT is split\n        ['PRINT', 'e'],\n        ['PRINT', 'l'],\n        ['PRINT', 'l'],\n        ['PRINT', 'o'],\n        ['PRINT', ' '],\n        ['ESC %G'],\n        ['PRINT', 'w'],\n        ['PRINT', 'o'],\n        ['PRINT', 'r'],\n        ['ESC E'],\n        ['PRINT', 'l'],\n        ['PRINT', 'd'],\n        ['PRINT', '!'],\n        ['SGR', [0]],\n        ['EXE \\r'],\n        ['EXE \\n'],\n        ['PRINT', '$'],\n        ['PRINT', '>'],\n        ['DCS a', ['xyz', [1, 2]]],\n        ['OSC 1', 'foo=bar'],\n        ['PRINT', 'F'],\n        ['PRINT', 'I'],\n        ['PRINT', 'N']\n      ];\n\n      // split to single char input\n      for (let i = 0; i < INPUT.length; ++i) {\n        // Note: a single fully awaited parse call always ends in sync mode,\n        // which re-enables faster sync processing in the higher up callstack\n        await parseP(parser, INPUT[i]);\n      }\n      assert.deepEqual(callstack, RESULT);\n      evalStackSaves(parser.trackedStack, [\n        [0, ParserStackType.CSI, 0],\n        [0, ParserStackType.ESC, 0],\n        [0, ParserStackType.ESC, 0],\n        [0, ParserStackType.CSI, 0],\n        [0, ParserStackType.DCS, 0],\n        [0, ParserStackType.OSC, 0]\n      ]);\n    });\n    it('multiple async SGR handlers', async () => {\n      // register with fallback\n      const SGR2 = parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['2# SGR', params.toArray()]); return false; });\n      await parseP(parser, INPUT);\n      // should contain [2# SGR, SGR] call pairs\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# SGR') assert.equal(callstack[i + 1][0], 'SGR', 'Should fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 1],\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 1],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      SGR2.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n\n      // register without fallback\n      const SGR22 = parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['2# SGR', params.toArray()]); return true; });\n      await parseP(parser, INPUT);\n      // should only contain 2# SGR\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# SGR') assert.notEqual(callstack[i + 1][0], 'SGR', 'Should not fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 1],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 1],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      SGR22.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n    });\n    it('multiple async ESC handlers', async () => {\n      // register with fallback\n      const ESC2 = parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['2# ESC E']); return false; });\n      await parseP(parser, INPUT);\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# ESC E') assert.equal(callstack[i + 1][0], 'ESC E', 'Should fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 1],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      ESC2.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n\n      // register without fallback\n      const ESC22 = parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['2# ESC E']); return true; });\n      await parseP(parser, INPUT);\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# ESC E') assert.notEqual(callstack[i + 1][0], 'ESC E', 'Should not fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 1],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      ESC22.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n    });\n    it('sync/async SGR mixed', async () => {\n      // sync with fallback\n      const SGR2 = parser.registerCsiHandler({ final: 'm' }, params => { callstack.push(['2# SGR', params.toArray()]); return false; });\n      // async with fallback\n      const SGR3 = parser.registerCsiHandler({ final: 'm' }, async params => { callstack.push(['3# SGR', params.toArray()]); return false; });\n      await parseP(parser, INPUT);\n      // should contain [3# SGR, 2# SGR, SGR] call triples\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '3# SGR') {\n          assert.equal(callstack[i + 1][0], '2# SGR', 'Should fallback to next handler');\n          assert.equal(callstack[i + 2][0], 'SGR', 'Should fallback to original handler');\n        }\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 2],\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 2],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // dispose SGR2 (sync one)\n      SGR2.dispose();\n      await parseP(parser, INPUT);\n      // should contain [3# SGR, SGR] call pairs\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '3# SGR') {\n          assert.equal(callstack[i + 1][0], 'SGR', 'Should fallback to original handler');\n        }\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 1],\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 1],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // dispose SGR3 (async one)\n      SGR3.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n    });\n    it('multiple async OSC handlers', async () => {\n      // register with fallback\n      const OSC2 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return false; }));\n      await parseP(parser, INPUT);\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# OSC 1') assert.equal(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      OSC2.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n\n      // register without fallback\n      const OSC22 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return true; }));\n      await parseP(parser, INPUT);\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# OSC 1') assert.notEqual(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      OSC22.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n    });\n    it('multiple async DCS handlers', async () => {\n      // register with fallback\n      const DCS2 = parser.registerDcsHandler({ final: 'a' }, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return false; }));\n      await parseP(parser, INPUT);\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# DCS a') assert.equal(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      DCS2.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n\n      // register without fallback\n      const DCS22 = parser.registerDcsHandler({ final: 'a' }, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return true; }));\n      await parseP(parser, INPUT);\n      for (let i = 0; i < callstack.length; ++i) {\n        const entry = callstack[i];\n        if (entry[0] === '2# DCS a') assert.notEqual(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler');\n      }\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n      // after dispose we should be back to RESULT\n      DCS22.dispose();\n      await parseP(parser, INPUT);\n      assert.deepEqual(callstack, RESULT, 'Should not call custom handler');\n      evalStackSaves(parser.trackedStack, [\n        [6, ParserStackType.CSI, 0],\n        [15, ParserStackType.ESC, 0],\n        [20, ParserStackType.ESC, 0],\n        [27, ParserStackType.CSI, 0],\n        [41, ParserStackType.DCS, 0],\n        [54, ParserStackType.OSC, 0]\n      ]);\n      clearAccu();\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/parser/EscapeSequenceParser.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType, IApcHandler, IApcParser, ApcFallbackHandlerType } from 'common/parser/Types';\nimport { ParserState, ParserAction } from 'common/parser/Constants';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IDisposable } from 'common/Types';\nimport { Params } from 'common/parser/Params';\nimport { OscParser } from 'common/parser/OscParser';\nimport { DcsParser } from 'common/parser/DcsParser';\nimport { ApcParser } from 'common/parser/ApcParser';\n\n/**\n * Table values are generated like this:\n *    index:  currentState << TableValue.INDEX_STATE_SHIFT | charCode\n *    value:  action << TableValue.TRANSITION_ACTION_SHIFT | nextState\n */\nconst enum TableAccess {\n  TRANSITION_ACTION_SHIFT = 8,\n  TRANSITION_STATE_MASK = 255,\n  INDEX_STATE_SHIFT = 8\n}\n\n/**\n * Transition table for EscapeSequenceParser.\n */\nexport class TransitionTable {\n  public table: Uint16Array;\n\n  constructor(length: number) {\n    this.table = new Uint16Array(length);\n  }\n\n  /**\n   * Set default transition.\n   * @param action default action\n   * @param next default next state\n   */\n  public setDefault(action: ParserAction, next: ParserState): void {\n    this.table.fill(action << TableAccess.TRANSITION_ACTION_SHIFT | next);\n  }\n\n  /**\n   * Add a transition to the transition table.\n   * @param code input character code\n   * @param state current parser state\n   * @param action parser action to be done\n   * @param next next parser state\n   */\n  public add(code: number, state: ParserState, action: ParserAction, next: ParserState): void {\n    this.table[state << TableAccess.INDEX_STATE_SHIFT | code] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;\n  }\n\n  /**\n   * Add transitions for multiple input character codes.\n   * @param codes input character code array\n   * @param state current parser state\n   * @param action parser action to be done\n   * @param next next parser state\n   */\n  public addMany(codes: number[], state: ParserState, action: ParserAction, next: ParserState): void {\n    for (let i = 0; i < codes.length; i++) {\n      this.table[state << TableAccess.INDEX_STATE_SHIFT | codes[i]] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;\n    }\n  }\n}\n\n\n// Pseudo-character placeholder for printable non-ascii characters (unicode).\nconst NON_ASCII_PRINTABLE = 0xA0;\n\n\n/**\n * VT500 compatible transition table.\n * Taken from https://vt100.net/emu/dec_ansi_parser.\n */\nexport const VT500_TRANSITION_TABLE = (function (): TransitionTable {\n  const table: TransitionTable = new TransitionTable(4095);\n\n  // range macro for byte\n  const BYTE_VALUES = 256;\n  const blueprint = Array.apply(null, Array(BYTE_VALUES)).map((unused: any, i: number) => i);\n  const r = (start: number, end: number): number[] => blueprint.slice(start, end);\n\n  // Default definitions.\n  const PRINTABLES = r(0x20, 0x7f); // 0x20 (SP) included, 0x7F (DEL) excluded\n  const EXECUTABLES = r(0x00, 0x18);\n  EXECUTABLES.push(0x19);\n  EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));\n\n  const states: number[] = r(ParserState.GROUND, ParserState.STATE_LENGTH);\n\n  // set default transition\n  table.setDefault(ParserAction.ERROR, ParserState.GROUND);\n  // printables\n  table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);\n  // global anywhere rules\n  for (const state of states) {\n    table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);\n    table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);\n    table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);\n    table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator\n    table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE);  // ESC\n    table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING);  // OSC\n    table.addMany([0x98, 0x9e], state, ParserAction.IGNORE, ParserState.SOS_PM_STRING);  // SOS, PM\n    table.add(0x9f, state, ParserAction.APC_START, ParserState.APC_STRING);  // APC\n    table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY);  // CSI\n    table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY);  // DCS\n  }\n  // rules for executables and 7f\n  table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND);\n  table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE);\n  table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE);\n  table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);\n  table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY);\n  table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY);\n  table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM);\n  table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);\n  table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE);\n  table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE);\n  table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE);\n  table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE);\n  table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);\n  // osc\n  table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING);\n  table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);\n  table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);\n  table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND);\n  table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);\n  // sos/pm\n  table.addMany([0x58, 0x5e], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_STRING);\n  table.addMany(PRINTABLES, ParserState.SOS_PM_STRING, ParserAction.IGNORE, ParserState.SOS_PM_STRING);\n  table.addMany(EXECUTABLES, ParserState.SOS_PM_STRING, ParserAction.IGNORE, ParserState.SOS_PM_STRING);\n  table.add(0x9c, ParserState.SOS_PM_STRING, ParserAction.IGNORE, ParserState.GROUND);\n  table.add(0x7f, ParserState.SOS_PM_STRING, ParserAction.IGNORE, ParserState.SOS_PM_STRING);\n  // apc\n  table.add(0x5f, ParserState.ESCAPE, ParserAction.APC_START, ParserState.APC_STRING);\n  table.addMany(PRINTABLES, ParserState.APC_STRING, ParserAction.APC_PUT, ParserState.APC_STRING);\n  table.addMany(EXECUTABLES, ParserState.APC_STRING, ParserAction.IGNORE, ParserState.APC_STRING);\n  table.add(0x7f, ParserState.APC_STRING, ParserAction.IGNORE, ParserState.APC_STRING);\n  table.addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.APC_STRING, ParserAction.APC_END, ParserState.GROUND);\n  // csi entries\n  table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);\n  table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);\n  table.addMany(r(0x30, 0x3c), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);\n  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);\n  table.addMany(r(0x30, 0x3c), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);\n  table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);\n  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n  table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n  table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n  table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);\n  table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);\n  table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);\n  table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n  table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND);\n  table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);\n  // esc_intermediate\n  table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);\n  table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);\n  table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n  table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n  table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n  table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n  table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n  // dcs entry\n  table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);\n  table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);\n  table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);\n  table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);\n  table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);\n  table.addMany(r(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);\n  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);\n  table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n  table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n  table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n  table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);\n  table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);\n  table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);\n  table.addMany(r(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);\n  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n  table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);\n  table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);\n  table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);\n  table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);\n  table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);\n  table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n  table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);\n  table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);\n  table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);\n  table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);\n  table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);\n  table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);\n  table.addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);\n  // special handling of unicode chars\n  table.add(NON_ASCII_PRINTABLE, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);\n  table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);\n  table.add(NON_ASCII_PRINTABLE, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n  table.add(NON_ASCII_PRINTABLE, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n  table.add(NON_ASCII_PRINTABLE, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);\n  table.add(NON_ASCII_PRINTABLE, ParserState.APC_STRING, ParserAction.APC_PUT, ParserState.APC_STRING);\n  return table;\n})();\n\n\n/**\n * EscapeSequenceParser.\n * This class implements the ANSI/DEC compatible parser described by\n * Paul Williams (https://vt100.net/emu/dec_ansi_parser).\n *\n * To implement custom ANSI compliant escape sequences it is not needed to\n * alter this parser, instead consider registering a custom handler.\n * For non ANSI compliant sequences change the transition table with\n * the optional `transitions` constructor argument and\n * reimplement the `parse` method.\n *\n * This parser is currently hardcoded to operate in ZDM (Zero Default Mode)\n * as suggested by the original parser, thus empty parameters are set to 0.\n * This this is not in line with the latest ECMA-48 specification\n * (ZDM was part of the early specs and got completely removed later on).\n *\n * Other than the original parser from vt100.net this parser supports\n * sub parameters in digital parameters separated by colons. Empty sub parameters\n * are set to -1 (no ZDM for sub parameters).\n *\n * About prefix and intermediate bytes:\n * This parser follows the assumptions of the vt100.net parser with these restrictions:\n * - only one prefix byte is allowed as first parameter byte, byte range 0x3c .. 0x3f\n * - max. two intermediates are respected, byte range 0x20 .. 0x2f\n * Note that this is not in line with ECMA-48 which does not limit either of those.\n * Furthermore ECMA-48 allows the prefix byte range at any param byte position. Currently\n * there are no known sequences that follow the broader definition of the specification.\n *\n * TODO: implement error recovery hook via error handler return values\n */\nexport class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {\n  public initialState: number;\n  public currentState: number;\n  public precedingJoinState: number; // UnicodeJoinProperties\n\n  // buffers over several parse calls\n  protected _params: Params;\n  protected _collect: number;\n\n  // handler lookup containers\n  protected _printHandler: PrintHandlerType;\n  protected _executeHandlers: { [flag: number]: ExecuteHandlerType };\n  protected _csiHandlers: IHandlerCollection<CsiHandlerType>;\n  protected _escHandlers: IHandlerCollection<EscHandlerType>;\n  protected readonly _oscParser: IOscParser;\n  protected readonly _dcsParser: IDcsParser;\n  protected readonly _apcParser: IApcParser;\n  protected _errorHandler: (state: IParsingState) => IParsingState;\n\n  // fallback handlers\n  protected _printHandlerFb: PrintFallbackHandlerType;\n  protected _executeHandlerFb: ExecuteFallbackHandlerType;\n  protected _csiHandlerFb: CsiFallbackHandlerType;\n  protected _escHandlerFb: EscFallbackHandlerType;\n  protected _errorHandlerFb: (state: IParsingState) => IParsingState;\n\n  // parser stack save for async handler support\n  protected _parseStack: IParserStackState = {\n    state: ParserStackType.NONE,\n    handlers: [],\n    handlerPos: 0,\n    transition: 0,\n    chunkPos: 0\n  };\n\n  constructor(\n    protected readonly _transitions: TransitionTable = VT500_TRANSITION_TABLE\n  ) {\n    super();\n\n    this.initialState = ParserState.GROUND;\n    this.currentState = this.initialState;\n    this._params = new Params(); // defaults to 32 storable params/subparams\n    this._params.addParam(0);    // ZDM\n    this._collect = 0;\n    this.precedingJoinState = 0;\n\n    // set default fallback handlers and handler lookup containers\n    this._printHandlerFb = (data, start, end): void => { };\n    this._executeHandlerFb = (code: number): void => { };\n    this._csiHandlerFb = (ident: number, params: IParams): void => { };\n    this._escHandlerFb = (ident: number): void => { };\n    this._errorHandlerFb = (state: IParsingState): IParsingState => state;\n    this._printHandler = this._printHandlerFb;\n    this._executeHandlers = Object.create(null);\n    this._csiHandlers = Object.create(null);\n    this._escHandlers = Object.create(null);\n    this._register(toDisposable(() => {\n      this._csiHandlers = Object.create(null);\n      this._executeHandlers = Object.create(null);\n      this._escHandlers = Object.create(null);\n    }));\n    this._oscParser = this._register(new OscParser());\n    this._dcsParser = this._register(new DcsParser());\n    this._apcParser = this._register(new ApcParser());\n    this._errorHandler = this._errorHandlerFb;\n\n    // swallow 7bit ST (ESC+\\)\n    this.registerEscHandler({ final: '\\\\' }, () => true);\n  }\n\n  protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {\n    let res = 0;\n    if (id.prefix) {\n      if (id.prefix.length > 1) {\n        throw new Error('only one byte as prefix supported');\n      }\n      res = id.prefix.charCodeAt(0);\n      if (res && 0x3c > res || res > 0x3f) {\n        throw new Error('prefix must be in range 0x3c .. 0x3f');\n      }\n    }\n    if (id.intermediates) {\n      if (id.intermediates.length > 2) {\n        throw new Error('only two bytes as intermediates are supported');\n      }\n      for (let i = 0; i < id.intermediates.length; ++i) {\n        const intermediate = id.intermediates.charCodeAt(i);\n        if (0x20 > intermediate || intermediate > 0x2f) {\n          throw new Error('intermediate must be in range 0x20 .. 0x2f');\n        }\n        res <<= 8;\n        res |= intermediate;\n      }\n    }\n    if (id.final.length !== 1) {\n      throw new Error('final must be a single byte');\n    }\n    const finalCode = id.final.charCodeAt(0);\n    if (finalRange[0] > finalCode || finalCode > finalRange[1]) {\n      throw new Error(`final must be in range ${finalRange[0]} .. ${finalRange[1]}`);\n    }\n    res <<= 8;\n    res |= finalCode;\n\n    return res;\n  }\n\n  public identToString(ident: number): string {\n    const res: string[] = [];\n    while (ident) {\n      res.push(String.fromCharCode(ident & 0xFF));\n      ident >>= 8;\n    }\n    return res.reverse().join('');\n  }\n\n  public setPrintHandler(handler: PrintHandlerType): void {\n    this._printHandler = handler;\n  }\n  public clearPrintHandler(): void {\n    this._printHandler = this._printHandlerFb;\n  }\n\n  public registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable {\n    const ident = this._identifier(id, [0x30, 0x7e]);\n    this._escHandlers[ident] ??= [];\n    const handlerList = this._escHandlers[ident];\n    handlerList.push(handler);\n    return {\n      dispose: () => {\n        const handlerIndex = handlerList.indexOf(handler);\n        if (handlerIndex !== -1) {\n          handlerList.splice(handlerIndex, 1);\n        }\n      }\n    };\n  }\n  public clearEscHandler(id: IFunctionIdentifier): void {\n    if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];\n  }\n  public setEscHandlerFallback(handler: EscFallbackHandlerType): void {\n    this._escHandlerFb = handler;\n  }\n\n  public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {\n    this._executeHandlers[flag.charCodeAt(0)] = handler;\n  }\n  public clearExecuteHandler(flag: string): void {\n    if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];\n  }\n  public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {\n    this._executeHandlerFb = handler;\n  }\n\n  public registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {\n    const ident = this._identifier(id);\n    this._csiHandlers[ident] ??= [];\n    const handlerList = this._csiHandlers[ident];\n    handlerList.push(handler);\n    return {\n      dispose: () => {\n        const handlerIndex = handlerList.indexOf(handler);\n        if (handlerIndex !== -1) {\n          handlerList.splice(handlerIndex, 1);\n        }\n      }\n    };\n  }\n  public clearCsiHandler(id: IFunctionIdentifier): void {\n    if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];\n  }\n  public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {\n    this._csiHandlerFb = callback;\n  }\n\n  public registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {\n    return this._dcsParser.registerHandler(this._identifier(id), handler);\n  }\n  public clearDcsHandler(id: IFunctionIdentifier): void {\n    this._dcsParser.clearHandler(this._identifier(id));\n  }\n  public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {\n    this._dcsParser.setHandlerFallback(handler);\n  }\n\n  public registerOscHandler(ident: number, handler: IOscHandler): IDisposable {\n    return this._oscParser.registerHandler(ident, handler);\n  }\n  public clearOscHandler(ident: number): void {\n    this._oscParser.clearHandler(ident);\n  }\n  public setOscHandlerFallback(handler: OscFallbackHandlerType): void {\n    this._oscParser.setHandlerFallback(handler);\n  }\n\n  public registerApcHandler(ident: number, handler: IApcHandler): IDisposable {\n    return this._apcParser.registerHandler(ident, handler);\n  }\n  public clearApcHandler(ident: number): void {\n    this._apcParser.clearHandler(ident);\n  }\n  public setApcHandlerFallback(handler: ApcFallbackHandlerType): void {\n    this._apcParser.setHandlerFallback(handler);\n  }\n\n  public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {\n    this._errorHandler = callback;\n  }\n  public clearErrorHandler(): void {\n    this._errorHandler = this._errorHandlerFb;\n  }\n\n  /**\n   * Reset parser to initial values.\n   *\n   * This can also be used to lift the improper continuation error condition\n   * when dealing with async handlers. Use this only as a last resort to silence\n   * that error when the terminal has no pending data to be processed. Note that\n   * the interrupted async handler might continue its work in the future messing\n   * up the terminal state even further.\n   */\n  public reset(): void {\n    this.currentState = this.initialState;\n    this._oscParser.reset();\n    this._dcsParser.reset();\n    this._apcParser.reset();\n    this._params.reset();\n    this._params.addParam(0); // ZDM\n    this._collect = 0;\n    this.precedingJoinState = 0;\n    // abort pending continuation from async handler\n    // Here the RESET type indicates, that the next parse call will\n    // ignore any saved stack, instead continues sync with next codepoint from GROUND\n    if (this._parseStack.state !== ParserStackType.NONE) {\n      this._parseStack.state = ParserStackType.RESET;\n      this._parseStack.handlers = []; // also release handlers ref\n    }\n  }\n\n  /**\n   * Async parse support.\n   */\n  protected _preserveStack(\n    state: ParserStackType,\n    handlers: ResumableHandlersType,\n    handlerPos: number,\n    transition: number,\n    chunkPos: number\n  ): void {\n    this._parseStack.state = state;\n    this._parseStack.handlers = handlers;\n    this._parseStack.handlerPos = handlerPos;\n    this._parseStack.transition = transition;\n    this._parseStack.chunkPos = chunkPos;\n  }\n\n  /**\n   * Parse UTF32 codepoints in `data` up to `length`.\n   *\n   * Note: For several actions with high data load the parsing is optimized\n   * by using local read ahead loops with hardcoded conditions to\n   * avoid costly table lookups. Make sure that any change of table values\n   * will be reflected in the loop conditions as well and vice versa.\n   * Affected states/actions:\n   * - GROUND:PRINT\n   * - CSI_PARAM:PARAM\n   * - DCS_PARAM:PARAM\n   * - OSC_STRING:OSC_PUT\n   * - DCS_PASSTHROUGH:DCS_PUT\n   *\n   * Note on asynchronous handler support:\n   * Any handler returning a promise will be treated as asynchronous.\n   * To keep the in-band blocking working for async handlers, `parse` pauses execution,\n   * creates a stack save and returns the promise to the caller.\n   * For proper continuation of the paused state it is important\n   * to await the promise resolving. On resolve the parse must be repeated\n   * with the same chunk of data and the resolved value in `promiseResult`\n   * until no promise is returned.\n   *\n   * Important: With only sync handlers defined, parsing is completely synchronous as well.\n   * As soon as an async handler is involved, synchronous parsing is not possible anymore.\n   *\n   * Boilerplate for proper parsing of multiple chunks with async handlers:\n   *\n   * ```typescript\n   * async function parseMultipleChunks(chunks: Uint32Array[]): Promise<void> {\n   *   for (const chunk of chunks) {\n   *     let result: void | Promise<boolean>;\n   *     let prev: boolean | undefined;\n   *     while (result = parser.parse(chunk, chunk.length, prev)) {\n   *       prev = await result;\n   *     }\n   *   }\n   *   // finished parsing all chunks...\n   * }\n   * ```\n   */\n  public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean> {\n    let code = 0;\n    let transition = 0;\n    let start = 0;\n    let handlerResult: void | boolean | Promise<boolean>;\n\n    // resume from async handler\n    if (this._parseStack.state) {\n      // allow sync parser reset even in continuation mode\n      // Note: can be used to recover parser from improper continuation error below\n      if (this._parseStack.state === ParserStackType.RESET) {\n        this._parseStack.state = ParserStackType.NONE;\n        start = this._parseStack.chunkPos + 1; // continue with next codepoint in GROUND\n      } else {\n        if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {\n          /**\n           * Reject further parsing on improper continuation after pausing. This is a really bad\n           * condition with screwed up execution order and prolly messed up terminal state,\n           * therefore we exit hard with an exception and reject any further parsing.\n           *\n           * Note: With `Terminal.write` usage this exception should never occur, as the top level\n           * calls are guaranteed to handle async conditions properly. If you ever encounter this\n           * exception in your terminal integration it indicates, that you injected data chunks to\n           * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for\n           * continuation of a running async handler.\n           *\n           * It is possible to get rid of this error by calling `reset`. But dont rely on that, as\n           * the pending async handler still might mess up the terminal later. Instead fix the\n           * faulty async handling, so this error will not be thrown anymore.\n           */\n          this._parseStack.state = ParserStackType.FAIL;\n          throw new Error('improper continuation due to previous async handler, giving up parsing');\n        }\n\n        // we have to resume the old handler loop if:\n        // - return value of the promise was `false`\n        // - handlers are not exhausted yet\n        const handlers = this._parseStack.handlers;\n        let handlerPos = this._parseStack.handlerPos - 1;\n        switch (this._parseStack.state) {\n          case ParserStackType.CSI:\n            if (promiseResult === false && handlerPos > -1) {\n              for (; handlerPos >= 0; handlerPos--) {\n                handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params);\n                if (handlerResult === true) {\n                  break;\n                } else if (handlerResult instanceof Promise) {\n                  this._parseStack.handlerPos = handlerPos;\n                  return handlerResult;\n                }\n              }\n            }\n            this._parseStack.handlers = [];\n            break;\n          case ParserStackType.ESC:\n            if (promiseResult === false && handlerPos > -1) {\n              for (; handlerPos >= 0; handlerPos--) {\n                handlerResult = (handlers as EscHandlerType[])[handlerPos]();\n                if (handlerResult === true) {\n                  break;\n                } else if (handlerResult instanceof Promise) {\n                  this._parseStack.handlerPos = handlerPos;\n                  return handlerResult;\n                }\n              }\n            }\n            this._parseStack.handlers = [];\n            break;\n          case ParserStackType.DCS:\n            code = data[this._parseStack.chunkPos];\n            handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult);\n            if (handlerResult) {\n              return handlerResult;\n            }\n            if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;\n            this._params.reset();\n            this._params.addParam(0); // ZDM\n            this._collect = 0;\n            break;\n          case ParserStackType.OSC:\n            code = data[this._parseStack.chunkPos];\n            handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult);\n            if (handlerResult) {\n              return handlerResult;\n            }\n            if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;\n            this._params.reset();\n            this._params.addParam(0); // ZDM\n            this._collect = 0;\n            break;\n          case ParserStackType.APC:\n            code = data[this._parseStack.chunkPos];\n            handlerResult = this._apcParser.end(code !== 0x18 && code !== 0x1a, promiseResult);\n            if (handlerResult) {\n              return handlerResult;\n            }\n            if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;\n            this._params.reset();\n            this._params.addParam(0); // ZDM\n            this._collect = 0;\n            break;\n        }\n        // cleanup before continuing with the main sync loop\n        this._parseStack.state = ParserStackType.NONE;\n        start = this._parseStack.chunkPos + 1;\n        this.precedingJoinState = 0;\n        this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;\n      }\n    }\n\n    // continue with main sync loop\n\n    // process input string\n    for (let i = start; i < length; ++i) {\n      code = data[i];\n\n      // normal transition & action lookup\n      transition = this._transitions.table[this.currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];\n      switch (transition >> TableAccess.TRANSITION_ACTION_SHIFT) {\n        case ParserAction.PRINT:\n          // read ahead with loop unrolling\n          // Note: 0x20 (SP) is included, 0x7F (DEL) is excluded\n          for (let j = i + 1; ; ++j) {\n            if (j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {\n              this._printHandler(data, i, j);\n              i = j - 1;\n              break;\n            }\n            if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {\n              this._printHandler(data, i, j);\n              i = j - 1;\n              break;\n            }\n            if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {\n              this._printHandler(data, i, j);\n              i = j - 1;\n              break;\n            }\n            if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {\n              this._printHandler(data, i, j);\n              i = j - 1;\n              break;\n            }\n          }\n          break;\n        case ParserAction.EXECUTE:\n          if (this._executeHandlers[code]) this._executeHandlers[code]();\n          else this._executeHandlerFb(code);\n          this.precedingJoinState = 0;\n          break;\n        case ParserAction.IGNORE:\n          break;\n        case ParserAction.ERROR:\n          const inject: IParsingState = this._errorHandler(\n            {\n              position: i,\n              code,\n              currentState: this.currentState,\n              collect: this._collect,\n              params: this._params,\n              abort: false\n            });\n          if (inject.abort) return;\n          // inject values: currently not implemented\n          break;\n        case ParserAction.CSI_DISPATCH:\n          // Trigger CSI Handler\n          const handlers = this._csiHandlers[this._collect << 8 | code];\n          let j = handlers ? handlers.length - 1 : -1;\n          for (; j >= 0; j--) {\n            // true means success and to stop bubbling\n            // a promise indicates an async handler that needs to finish before progressing\n            handlerResult = handlers[j](this._params);\n            if (handlerResult === true) {\n              break;\n            } else if (handlerResult instanceof Promise) {\n              this._preserveStack(ParserStackType.CSI, handlers, j, transition, i);\n              return handlerResult;\n            }\n          }\n          if (j < 0) {\n            this._csiHandlerFb(this._collect << 8 | code, this._params);\n          }\n          this.precedingJoinState = 0;\n          break;\n        case ParserAction.PARAM:\n          // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)\n          do {\n            switch (code) {\n              case 0x3b:\n                this._params.addParam(0);  // ZDM\n                break;\n              case 0x3a:\n                this._params.addSubParam(-1);\n                break;\n              default:  // 0x30 - 0x39\n                this._params.addDigit(code - 48);\n            }\n          } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c);\n          i--;\n          break;\n        case ParserAction.COLLECT:\n          this._collect <<= 8;\n          this._collect |= code;\n          break;\n        case ParserAction.ESC_DISPATCH:\n          const handlersEsc = this._escHandlers[this._collect << 8 | code];\n          let jj = handlersEsc ? handlersEsc.length - 1 : -1;\n          for (; jj >= 0; jj--) {\n            // true means success and to stop bubbling\n            // a promise indicates an async handler that needs to finish before progressing\n            handlerResult = handlersEsc[jj]();\n            if (handlerResult === true) {\n              break;\n            } else if (handlerResult instanceof Promise) {\n              this._preserveStack(ParserStackType.ESC, handlersEsc, jj, transition, i);\n              return handlerResult;\n            }\n          }\n          if (jj < 0) {\n            this._escHandlerFb(this._collect << 8 | code);\n          }\n          this.precedingJoinState = 0;\n          break;\n        case ParserAction.CLEAR:\n          this._params.reset();\n          this._params.addParam(0); // ZDM\n          this._collect = 0;\n          break;\n        case ParserAction.DCS_HOOK:\n          this._dcsParser.hook(this._collect << 8 | code, this._params);\n          break;\n        case ParserAction.DCS_PUT:\n          // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f\n          // unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort)\n          for (let j = i + 1; ; ++j) {\n            if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {\n              this._dcsParser.put(data, i, j);\n              i = j - 1;\n              break;\n            }\n          }\n          break;\n        case ParserAction.DCS_UNHOOK:\n          handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a);\n          if (handlerResult) {\n            this._preserveStack(ParserStackType.DCS, [], 0, transition, i);\n            return handlerResult;\n          }\n          if (code === 0x1b) transition |= ParserState.ESCAPE;\n          this._params.reset();\n          this._params.addParam(0); // ZDM\n          this._collect = 0;\n          this.precedingJoinState = 0;\n          break;\n        case ParserAction.OSC_START:\n          this._oscParser.start();\n          break;\n        case ParserAction.OSC_PUT:\n          // inner loop: 0x20 (SP) included, 0x7F (DEL) included\n          for (let j = i + 1; ; j++) {\n            if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {\n              this._oscParser.put(data, i, j);\n              i = j - 1;\n              break;\n            }\n          }\n          break;\n        case ParserAction.OSC_END:\n          handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a);\n          if (handlerResult) {\n            this._preserveStack(ParserStackType.OSC, [], 0, transition, i);\n            return handlerResult;\n          }\n          if (code === 0x1b) transition |= ParserState.ESCAPE;\n          this._params.reset();\n          this._params.addParam(0); // ZDM\n          this._collect = 0;\n          this.precedingJoinState = 0;\n          break;\n        case ParserAction.APC_START:\n          this._apcParser.start();\n          break;\n        case ParserAction.APC_PUT:\n          // inner loop - exit APC_PUT: 0x18, 0x1a, 0x1b, 0x9c\n          for (let j = i + 1; ; ++j) {\n            if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || code === 0x9c || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {\n              this._apcParser.put(data, i, j);\n              i = j - 1;\n              break;\n            }\n          }\n          break;\n        case ParserAction.APC_END:\n          handlerResult = this._apcParser.end(code !== 0x18 && code !== 0x1a);\n          if (handlerResult) {\n            this._preserveStack(ParserStackType.APC, [], 0, transition, i);\n            return handlerResult;\n          }\n          if (code === 0x1b) transition |= ParserState.ESCAPE;\n          this._params.reset();\n          this._params.addParam(0); // ZDM\n          this._collect = 0;\n          this.precedingJoinState = 0;\n          break;\n      }\n      this.currentState = transition & TableAccess.TRANSITION_STATE_MASK;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/parser/OscParser.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { OscParser, OscHandler } from 'common/parser/OscParser';\nimport { StringToUtf32, utf32ToString } from 'common/input/TextDecoder';\nimport { IOscHandler } from 'common/parser/Types';\n\nfunction toUtf32(s: string): Uint32Array {\n  const utf32 = new Uint32Array(s.length);\n  const decoder = new StringToUtf32();\n  const length = decoder.decode(s, utf32);\n  return utf32.subarray(0, length);\n}\n\nclass TestHandler implements IOscHandler {\n  constructor(public id: number, public output: any[], public msg: string, public returnFalse: boolean = false) {}\n  public start(): void {\n    this.output.push([this.msg, this.id, 'START']);\n  }\n  public put(data: Uint32Array, start: number, end: number): void {\n    this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]);\n  }\n  public end(success: boolean): boolean {\n    this.output.push([this.msg, this.id, 'END', success]);\n    if (this.returnFalse) {\n      return false;\n    }\n    return true;\n  }\n}\n\ndescribe('OscParser', () => {\n  let parser: OscParser;\n  let reports: any[] = [];\n  beforeEach(() => {\n    reports = [];\n    parser = new OscParser();\n    parser.setHandlerFallback((id, action, data) => {\n      reports.push([id, action, data]);\n    });\n  });\n  describe('identifier parsing', () => {\n    it('no report for illegal ids', () => {\n      const data = toUtf32('hello world!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, []);\n    });\n    it('no payload', () => {\n      parser.start();\n      let data = toUtf32('12');\n      parser.put(data, 0, data.length);\n      data = toUtf32('34');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [[1234, 'START', undefined], [1234, 'END', true]]);\n    });\n    it('with payload', () => {\n      parser.start();\n      let data = toUtf32('12');\n      parser.put(data, 0, data.length);\n      data = toUtf32('34');\n      parser.put(data, 0, data.length);\n      data = toUtf32(';h');\n      parser.put(data, 0, data.length);\n      data = toUtf32('ello');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        [1234, 'START', undefined],\n        [1234, 'PUT', 'h'],\n        [1234, 'PUT', 'ello'],\n        [1234, 'END', true]\n      ]);\n    });\n  });\n  describe('handler registration', () => {\n    it('setOscHandler', () => {\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th'));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        // messages from TestHandler\n        ['th', 1234, 'START'],\n        ['th', 1234, 'PUT', 'Here comes'],\n        ['th', 1234, 'PUT', 'the mouse!'],\n        ['th', 1234, 'END', true]\n      ]);\n    });\n    it('clearOscHandler', () => {\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th'));\n      parser.clearHandler(1234);\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        // messages from fallback handler\n        [1234, 'START', undefined],\n        [1234, 'PUT', 'Here comes'],\n        [1234, 'PUT', 'the mouse!'],\n        [1234, 'END', true]\n      ]);\n    });\n    it('addOscHandler', () => {\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th1'));\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th2'));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        ['th2', 1234, 'START'],\n        ['th1', 1234, 'START'],\n        ['th2', 1234, 'PUT', 'Here comes'],\n        ['th1', 1234, 'PUT', 'Here comes'],\n        ['th2', 1234, 'PUT', 'the mouse!'],\n        ['th1', 1234, 'PUT', 'the mouse!'],\n        ['th2', 1234, 'END', true],\n        ['th1', 1234, 'END', false]  // false due being already handled by th2!\n      ]);\n    });\n    it('addOscHandler with return false', () => {\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th1'));\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th2', true));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        ['th2', 1234, 'START'],\n        ['th1', 1234, 'START'],\n        ['th2', 1234, 'PUT', 'Here comes'],\n        ['th1', 1234, 'PUT', 'Here comes'],\n        ['th2', 1234, 'PUT', 'the mouse!'],\n        ['th1', 1234, 'PUT', 'the mouse!'],\n        ['th2', 1234, 'END', true],\n        ['th1', 1234, 'END', true]  // true since th2 indicated to keep bubbling\n      ]);\n    });\n    it('dispose handlers', () => {\n      parser.registerHandler(1234, new TestHandler(1234, reports, 'th1'));\n      const dispo = parser.registerHandler(1234, new TestHandler(1234, reports, 'th2', true));\n      dispo.dispose();\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32('the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [\n        ['th1', 1234, 'START'],\n        ['th1', 1234, 'PUT', 'Here comes'],\n        ['th1', 1234, 'PUT', 'the mouse!'],\n        ['th1', 1234, 'END', true]\n      ]);\n    });\n  });\n  describe('OscHandlerFactory', () => {\n    const TEST_PAYLOAD_LIMIT = 100;\n    const CHUNK_SIZE = 10;\n    let originalPayloadLimit: number;\n\n    beforeEach(() => {\n      const handlerConstructor = OscHandler as unknown as { _payloadLimit: number };\n      originalPayloadLimit = handlerConstructor._payloadLimit;\n      handlerConstructor._payloadLimit = TEST_PAYLOAD_LIMIT;\n    });\n\n    afterEach(() => {\n      const handlerConstructor = OscHandler as unknown as { _payloadLimit: number };\n      handlerConstructor._payloadLimit = originalPayloadLimit;\n    });\n\n    it('should be called once on end(true)', () => {\n      parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; }));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]);\n    });\n    it('should not be called on end(false)', () => {\n      parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; }));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(false);\n      assert.deepEqual(reports, []);\n    });\n    it('should be disposable', () => {\n      parser.registerHandler(1234, new OscHandler(data => { reports.push(['one', data]); return true; }));\n      const dispo = parser.registerHandler(1234, new OscHandler(data => { reports.push(['two', data]); return true; }));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [['two', 'Here comes the mouse!']]);\n      dispo.dispose();\n      parser.start();\n      data = toUtf32('1234;some other');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' data');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]);\n    });\n    it('should respect return false', () => {\n      parser.registerHandler(1234, new OscHandler(data => { reports.push(['one', data]); return true; }));\n      parser.registerHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; }));\n      parser.start();\n      let data = toUtf32('1234;Here comes');\n      parser.put(data, 0, data.length);\n      data = toUtf32(' the mouse!');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'Here comes the mouse!']]);\n    });\n    it('should work up to payload limit', function(): void {\n      this.timeout(30000);\n      parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; }));\n      parser.start();\n      let data = toUtf32('1234;');\n      parser.put(data, 0, data.length);\n      data = toUtf32('A'.repeat(CHUNK_SIZE));\n      for (let i = 0; i < TEST_PAYLOAD_LIMIT; i += CHUNK_SIZE) {\n        parser.put(data, 0, data.length);\n      }\n      parser.end(true);\n      assert.deepEqual(reports, [[1234, 'A'.repeat(TEST_PAYLOAD_LIMIT)]]);\n    });\n    it('should abort for payload limit +1', function(): void {\n      this.timeout(30000);\n      parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; }));\n      parser.start();\n      let data = toUtf32('1234;');\n      parser.put(data, 0, data.length);\n      data = toUtf32('A'.repeat(CHUNK_SIZE));\n      for (let i = 0; i < TEST_PAYLOAD_LIMIT; i += CHUNK_SIZE) {\n        parser.put(data, 0, data.length);\n      }\n      data = toUtf32('A');\n      parser.put(data, 0, data.length);\n      parser.end(true);\n      assert.deepEqual(reports, []);\n    });\n  });\n});\n\n\nclass TestHandlerAsync implements IOscHandler {\n  constructor(public id: number, public output: any[], public msg: string, public returnFalse: boolean = false) {}\n  public start(): void {\n    this.output.push([this.msg, this.id, 'START']);\n  }\n  public put(data: Uint32Array, start: number, end: number): void {\n    this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]);\n  }\n  public async end(success: boolean): Promise<boolean> {\n    await Promise.resolve();\n    this.output.push([this.msg, this.id, 'END', success]);\n    if (this.returnFalse) {\n      return false;\n    }\n    return true;\n  }\n}\nasync function endP(parser: OscParser, success: boolean): Promise<void> {\n  let result: void | Promise<boolean>;\n  let prev: boolean | undefined;\n  while (result = parser.end(success, prev)) {\n    prev = await result;\n  }\n}\n\ndescribe('OscParser - async tests', () => {\n  let parser: OscParser;\n  let reports: any[] = [];\n  beforeEach(() => {\n    reports = [];\n    parser = new OscParser();\n    parser.setHandlerFallback((id, action, data) => {\n      reports.push([id, action, data]);\n    });\n  });\n  describe('sync and async mixed', () => {\n    describe('sync | async | sync', () => {\n      it('first should run, cleanup action for others', async () => {\n        parser.registerHandler(1234, new TestHandler(1234, reports, 's1'));\n        parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 'a1'));\n        parser.registerHandler(1234, new TestHandler(1234, reports, 's2'));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['s2', 1234, 'START'],\n          ['a1', 1234, 'START'],\n          ['s1', 1234, 'START'],\n          ['s2', 1234, 'PUT', 'Here comes'],\n          ['a1', 1234, 'PUT', 'Here comes'],\n          ['s1', 1234, 'PUT', 'Here comes'],\n          ['s2', 1234, 'PUT', 'the mouse!'],\n          ['a1', 1234, 'PUT', 'the mouse!'],\n          ['s1', 1234, 'PUT', 'the mouse!'],\n          ['s2', 1234, 'END', true],\n          ['a1', 1234, 'END', false],\n          ['s1', 1234, 'END', false]\n        ]);\n      });\n      it('all should run', async () => {\n        parser.registerHandler(1234, new TestHandler(1234, reports, 's1', true));\n        parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 'a1', true));\n        parser.registerHandler(1234, new TestHandler(1234, reports, 's2', true));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['s2', 1234, 'START'],\n          ['a1', 1234, 'START'],\n          ['s1', 1234, 'START'],\n          ['s2', 1234, 'PUT', 'Here comes'],\n          ['a1', 1234, 'PUT', 'Here comes'],\n          ['s1', 1234, 'PUT', 'Here comes'],\n          ['s2', 1234, 'PUT', 'the mouse!'],\n          ['a1', 1234, 'PUT', 'the mouse!'],\n          ['s1', 1234, 'PUT', 'the mouse!'],\n          ['s2', 1234, 'END', true],\n          ['a1', 1234, 'END', true],\n          ['s1', 1234, 'END', true]\n        ]);\n      });\n    });\n    describe('async | sync | async', () => {\n      it('first should run, cleanup action for others', async () => {\n        parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's1'));\n        parser.registerHandler(1234, new TestHandler(1234, reports, 'a1'));\n        parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's2'));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['s2', 1234, 'START'],\n          ['a1', 1234, 'START'],\n          ['s1', 1234, 'START'],\n          ['s2', 1234, 'PUT', 'Here comes'],\n          ['a1', 1234, 'PUT', 'Here comes'],\n          ['s1', 1234, 'PUT', 'Here comes'],\n          ['s2', 1234, 'PUT', 'the mouse!'],\n          ['a1', 1234, 'PUT', 'the mouse!'],\n          ['s1', 1234, 'PUT', 'the mouse!'],\n          ['s2', 1234, 'END', true],\n          ['a1', 1234, 'END', false],\n          ['s1', 1234, 'END', false]\n        ]);\n      });\n      it('all should run', async () => {\n        parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's1', true));\n        parser.registerHandler(1234, new TestHandler(1234, reports, 'a1', true));\n        parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's2', true));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32('the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [\n          // messages from TestHandler\n          ['s2', 1234, 'START'],\n          ['a1', 1234, 'START'],\n          ['s1', 1234, 'START'],\n          ['s2', 1234, 'PUT', 'Here comes'],\n          ['a1', 1234, 'PUT', 'Here comes'],\n          ['s1', 1234, 'PUT', 'Here comes'],\n          ['s2', 1234, 'PUT', 'the mouse!'],\n          ['a1', 1234, 'PUT', 'the mouse!'],\n          ['s1', 1234, 'PUT', 'the mouse!'],\n          ['s2', 1234, 'END', true],\n          ['a1', 1234, 'END', true],\n          ['s1', 1234, 'END', true]\n        ]);\n      });\n    });\n    describe('OscHandlerFactory', () => {\n      it('should be called once on end(true)', async () => {\n        parser.registerHandler(1234, new OscHandler(async data => { reports.push([1234, data]); return true; }));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        parser.end(true);\n        await endP(parser, true);\n        assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]);\n      });\n      it('should not be called on end(false)', async () => {\n        parser.registerHandler(1234, new OscHandler(async data => { reports.push([1234, data]); return true; }));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, false);\n        assert.deepEqual(reports, []);\n      });\n      it('should be disposable', async () => {\n        parser.registerHandler(1234, new OscHandler(async data => { reports.push(['one', data]); return true; }));\n        const dispo = parser.registerHandler(1234, new OscHandler(async data => { reports.push(['two', data]); return true; }));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [['two', 'Here comes the mouse!']]);\n        dispo.dispose();\n        parser.start();\n        data = toUtf32('1234;some other');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' data');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]);\n      });\n      it('should respect return false', async () => {\n        parser.registerHandler(1234, new OscHandler(async data => { reports.push(['one', data]); return true; }));\n        parser.registerHandler(1234, new OscHandler(async data => { reports.push(['two', data]); return false; }));\n        parser.start();\n        let data = toUtf32('1234;Here comes');\n        parser.put(data, 0, data.length);\n        data = toUtf32(' the mouse!');\n        parser.put(data, 0, data.length);\n        await endP(parser, true);\n        assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'Here comes the mouse!']]);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/parser/OscParser.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser, ISubParserStackState } from 'common/parser/Types';\nimport { OscState, ParserConstants } from 'common/parser/Constants';\nimport { utf32ToString } from 'common/input/TextDecoder';\nimport { IDisposable } from 'common/Types';\n\nconst EMPTY_HANDLERS: IOscHandler[] = [];\n\nexport class OscParser implements IOscParser {\n  private _state = OscState.START;\n  private _active = EMPTY_HANDLERS;\n  private _id = -1;\n  private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);\n  private _handlerFb: OscFallbackHandlerType = () => { };\n  private _stack: ISubParserStackState = {\n    paused: false,\n    loopPosition: 0,\n    fallThrough: false\n  };\n\n  public registerHandler(ident: number, handler: IOscHandler): IDisposable {\n    this._handlers[ident] ??= [];\n    const handlerList = this._handlers[ident];\n    handlerList.push(handler);\n    return {\n      dispose: () => {\n        const handlerIndex = handlerList.indexOf(handler);\n        if (handlerIndex !== -1) {\n          handlerList.splice(handlerIndex, 1);\n        }\n      }\n    };\n  }\n  public clearHandler(ident: number): void {\n    if (this._handlers[ident]) delete this._handlers[ident];\n  }\n  public setHandlerFallback(handler: OscFallbackHandlerType): void {\n    this._handlerFb = handler;\n  }\n\n  public dispose(): void {\n    this._handlers = Object.create(null);\n    this._handlerFb = () => { };\n    this._active = EMPTY_HANDLERS;\n  }\n\n  public reset(): void {\n    // force cleanup handlers if payload was already sent\n    if (this._state === OscState.PAYLOAD) {\n      for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {\n        this._active[j].end(false);\n      }\n    }\n    this._stack.paused = false;\n    this._active = EMPTY_HANDLERS;\n    this._id = -1;\n    this._state = OscState.START;\n  }\n\n  private _start(): void {\n    this._active = this._handlers[this._id] || EMPTY_HANDLERS;\n    if (!this._active.length) {\n      this._handlerFb(this._id, 'START');\n    } else {\n      for (let j = this._active.length - 1; j >= 0; j--) {\n        this._active[j].start();\n      }\n    }\n  }\n\n  private _put(data: Uint32Array, start: number, end: number): void {\n    if (!this._active.length) {\n      this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));\n    } else {\n      for (let j = this._active.length - 1; j >= 0; j--) {\n        this._active[j].put(data, start, end);\n      }\n    }\n  }\n\n  public start(): void {\n    // always reset leftover handlers\n    this.reset();\n    this._state = OscState.ID;\n  }\n\n  /**\n   * Put data to current OSC command.\n   * Expects the identifier of the OSC command in the form\n   * OSC id ; payload ST/BEL\n   * Payload chunks are not further processed and get\n   * directly passed to the handlers.\n   */\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._state === OscState.ABORT) {\n      return;\n    }\n    if (this._state === OscState.ID) {\n      while (start < end) {\n        const code = data[start++];\n        if (code === 0x3b) {\n          this._state = OscState.PAYLOAD;\n          this._start();\n          break;\n        }\n        if (code < 0x30 || 0x39 < code) {\n          this._state = OscState.ABORT;\n          return;\n        }\n        if (this._id === -1) {\n          this._id = 0;\n        }\n        this._id = this._id * 10 + code - 48;\n      }\n    }\n    if (this._state === OscState.PAYLOAD && end - start > 0) {\n      this._put(data, start, end);\n    }\n  }\n\n  /**\n   * Indicates end of an OSC command.\n   * Whether the OSC got aborted or finished normally\n   * is indicated by `success`.\n   */\n  public end(success: boolean, promiseResult: boolean = true): void | Promise<boolean> {\n    if (this._state === OscState.START) {\n      return;\n    }\n    // do nothing if command was faulty\n    if (this._state !== OscState.ABORT) {\n      // if we are still in ID state and get an early end\n      // means that the command has no payload thus we still have\n      // to announce START and send END right after\n      if (this._state === OscState.ID) {\n        this._start();\n      }\n\n      if (!this._active.length) {\n        this._handlerFb(this._id, 'END', success);\n      } else {\n        let handlerResult: boolean | Promise<boolean> = false;\n        let j = this._active.length - 1;\n        let fallThrough = false;\n        if (this._stack.paused) {\n          j = this._stack.loopPosition - 1;\n          handlerResult = promiseResult;\n          fallThrough = this._stack.fallThrough;\n          this._stack.paused = false;\n        }\n        if (!fallThrough && handlerResult === false) {\n          for (; j >= 0; j--) {\n            handlerResult = this._active[j].end(success);\n            if (handlerResult === true) {\n              break;\n            } else if (handlerResult instanceof Promise) {\n              this._stack.paused = true;\n              this._stack.loopPosition = j;\n              this._stack.fallThrough = false;\n              return handlerResult;\n            }\n          }\n          j--;\n        }\n        // cleanup left over handlers\n        // we always have to call .end for proper cleanup,\n        // here we use `success` to indicate whether a handler should execute\n        for (; j >= 0; j--) {\n          handlerResult = this._active[j].end(false);\n          if (handlerResult instanceof Promise) {\n            this._stack.paused = true;\n            this._stack.loopPosition = j;\n            this._stack.fallThrough = true;\n            return handlerResult;\n          }\n        }\n      }\n\n    }\n    this._active = EMPTY_HANDLERS;\n    this._id = -1;\n    this._state = OscState.START;\n  }\n}\n\n/**\n * Convenient class to allow attaching string based handler functions\n * as OSC handlers.\n */\nexport class OscHandler implements IOscHandler {\n  private static _payloadLimit = ParserConstants.PAYLOAD_LIMIT;\n\n  private _data = '';\n  private _hitLimit: boolean = false;\n\n  constructor(private _handler: (data: string) => boolean | Promise<boolean>) { }\n\n  public start(): void {\n    this._data = '';\n    this._hitLimit = false;\n  }\n\n  public put(data: Uint32Array, start: number, end: number): void {\n    if (this._hitLimit) {\n      return;\n    }\n    this._data += utf32ToString(data, start, end);\n    if (this._data.length > OscHandler._payloadLimit) {\n      this._data = '';\n      this._hitLimit = true;\n    }\n  }\n\n  public end(success: boolean): boolean | Promise<boolean> {\n    let ret: boolean | Promise<boolean> = false;\n    if (this._hitLimit) {\n      ret = false;\n    } else if (success) {\n      ret = this._handler(this._data);\n      if (ret instanceof Promise) {\n        // need to hold data until `ret` got resolved\n        // dont care for errors, data will be freed anyway on next start\n        return ret.then(res => {\n          this._data = '';\n          this._hitLimit = false;\n          return res;\n        });\n      }\n    }\n    this._data = '';\n    this._hitLimit = false;\n    return ret;\n  }\n}\n"
  },
  {
    "path": "src/common/parser/Params.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { assert } from 'chai';\nimport { Params } from 'common/parser/Params';\nimport { ParamsArray } from 'common/parser/Types';\n\nclass TestParams extends Params {\n  public get subParams(): Int32Array {\n    return this._subParams;\n  }\n  public get subParamsLength(): number {\n    return this._subParamsLength;\n  }\n}\n\n/** `Params` parser shim */\nfunction parse(params: Params, s: string | string[]): void {\n  params.reset();\n  params.addParam(0);\n  if (typeof s === 'string') {\n    s = [s];\n  }\n  for (const chunk of s) {\n    for (let i = 0; i < chunk.length; ++i) {\n      let code = chunk.charCodeAt(i);\n      do {\n        switch (code) {\n          case 0x3b:\n            params.addParam(0);\n            break;\n          case 0x3a:\n            params.addSubParam(-1);\n            break;\n          default:  // 0x30 - 0x39\n            params.addDigit(code - 48);\n        }\n      } while (++i < s.length && (code = chunk.charCodeAt(i)) > 0x2f && code < 0x3c);\n      i--;\n    }\n  }\n}\n\n\ndescribe('Params', () => {\n  it('should respect ctor args', () => {\n    const params = new TestParams(12, 23);\n    assert.equal(params.params.length, 12);\n    assert.equal(params.subParams.length, 23);\n    assert.deepEqual(params.toArray(), []);\n  });\n  it('addParam', () => {\n    const params = new TestParams();\n    params.addParam(1);\n    assert.equal(params.length, 1);\n    assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1]);\n    assert.deepEqual(params.toArray(), [1]);\n    params.addParam(23);\n    assert.equal(params.length, 2);\n    assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1, 23]);\n    assert.deepEqual(params.toArray(), [1, 23]);\n    assert.equal(params.subParamsLength, 0);\n  });\n  it('addSubParam', () => {\n    const params = new TestParams();\n    params.addParam(1);\n    params.addSubParam(2);\n    params.addSubParam(3);\n    assert.equal(params.length, 1);\n    assert.equal(params.subParamsLength, 2);\n    assert.deepEqual(params.toArray(), [1, [2, 3]]);\n    params.addParam(12345);\n    params.addSubParam(-1);\n    assert.equal(params.length, 2);\n    assert.equal(params.subParamsLength, 3);\n    assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]);\n  });\n  it('should not add sub params without previous param', () => {\n    const params = new TestParams();\n    params.addSubParam(2);\n    params.addSubParam(3);\n    assert.equal(params.length, 0);\n    assert.equal(params.subParamsLength, 0);\n    assert.deepEqual(params.toArray(), []);\n    params.addParam(1);\n    params.addSubParam(2);\n    params.addSubParam(3);\n    assert.equal(params.length, 1);\n    assert.equal(params.subParamsLength, 2);\n    assert.deepEqual(params.toArray(), [1, [2, 3]]);\n  });\n  it('reset', () => {\n    const params = new TestParams();\n    params.addParam(1);\n    params.addSubParam(2);\n    params.addSubParam(3);\n    params.addParam(12345);\n    params.addSubParam(-1);\n    params.reset();\n    assert.equal(params.length, 0);\n    assert.equal(params.subParamsLength, 0);\n    assert.deepEqual(params.toArray(), []);\n    params.addParam(1);\n    params.addSubParam(2);\n    params.addSubParam(3);\n    params.addParam(12345);\n    params.addSubParam(-1);\n    assert.equal(params.length, 2);\n    assert.equal(params.subParamsLength, 3);\n    assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]);\n  });\n  it('Params.fromArray --> toArray', () => {\n    let data: ParamsArray = [];\n    assert.deepEqual(Params.fromArray(data).toArray(), data);\n    data = [1, [2, 3], 12345, [-1]];\n    assert.deepEqual(Params.fromArray(data).toArray(), data);\n    data = [38, 2, 50, 100, 150];\n    assert.deepEqual(Params.fromArray(data).toArray(), data);\n    data = [38, 2, 50, 100, [150]];\n    assert.deepEqual(Params.fromArray(data).toArray(), data);\n    data = [38, [2, 50, 100, 150]];\n    assert.deepEqual(Params.fromArray(data).toArray(), data);\n    // strip empty sub params\n    data = [38, [2, 50, 100, 150], 5, [], 6];\n    assert.deepEqual(Params.fromArray(data).toArray(), [38, [2, 50, 100, 150], 5, 6]);\n  });\n  it('clone', () => {\n    const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6, 1, [2, 3], 12345, [-1]]);\n    assert.deepEqual(params.clone(), params);\n  });\n  it('hasSubParams / getSubParams', () => {\n    const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6]);\n    assert.equal(params.hasSubParams(0), true);\n    assert.deepEqual(params.getSubParams(0), new Int32Array([2, 50, 100, 150]));\n    assert.equal(params.hasSubParams(1), false);\n    assert.deepEqual(params.getSubParams(1), null);\n    assert.equal(params.hasSubParams(2), false);\n    assert.deepEqual(params.getSubParams(2), null);\n  });\n  it('getSubParamsAll', () => {\n    const params = Params.fromArray([1, [2, 3], 7, 12345, [-1]]);\n    assert.deepEqual(params.getSubParamsAll(), {0: new Int32Array([2, 3]), 2: new Int32Array([-1])});\n  });\n  describe('parse tests', () => {\n    it('param defaults to 0 (ZDM - zero default mode)', () => {\n      const params = new Params();\n      parse(params, '');\n      assert.deepEqual(params.toArray(), [0]);\n    });\n    it('sub param defaults to -1', () => {\n      const params = new Params();\n      parse(params, ':');\n      assert.deepEqual(params.toArray(), [0, [-1]]);\n    });\n    it('should correctly reset on new sequence', () => {\n      const params = new Params();\n      parse(params, '1;2;3');\n      assert.deepEqual(params.toArray(), [1, 2, 3]);\n      parse(params, '4');\n      assert.deepEqual(params.toArray(), [4]);\n      parse(params, '4::123:5;6;7');\n      assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]);\n      parse(params, '');\n      assert.deepEqual(params.toArray(), [0]);\n    });\n    it('should handle length restrictions correctly', () => {\n      // restrict to 3 params and 3 sub params\n      const params = new Params(3, 3);\n      parse(params, '1;2;3');\n      assert.deepEqual(params.toArray(), [1, 2, 3]);\n      parse(params, '4');\n      assert.deepEqual(params.toArray(), [4]);\n      parse(params, '4::123:5;6;7');\n      assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]);\n      parse(params, '');\n      assert.deepEqual(params.toArray(), [0]);\n      // overlong params\n      parse(params, '1;2;3;4;5;6;7');\n      assert.deepEqual(params.toArray(), [1, 2, 3]);\n      // overlong sub params\n      parse(params, '4;38:2::50:100:150;48:5:22');\n      assert.deepEqual(params.toArray(), [4, 38, [2, -1, 50], 48]);\n    });\n    it('typical sequences', () => {\n      const params = new Params();\n      // SGR with semicolon syntax\n      parse(params, '0;4;38;2;50;100;150;48;5;22');\n      assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, 100, 150, 48, 5, 22]);\n      // SGR mixed style (partly wrong)\n      parse(params, '0;4;38;2;50:100:150;48;5:22');\n      assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, [100, 150], 48, 5, [22]]);\n      // SGR colon style\n      parse(params, '0;4;38:2::50:100:150;48:5:22');\n      assert.deepEqual(params.toArray(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]);\n    });\n  });\n  describe('should not overflow to negative', () => {\n    it('reject params lesser -1', () => {\n      const params = new Params();\n      params.addParam(-1);\n      assert.throws(() => params.addParam(-2), 'values lesser than -1 are not allowed');\n    });\n    it('reject subparams lesser -1', () => {\n      const params = new Params();\n      params.addParam(-1);\n      params.addSubParam(-1);\n      assert.throws(() => params.addSubParam(-2), 'values lesser than -1 are not allowed');\n      assert.deepEqual(params.toArray(), [-1, [-1]]);\n    });\n    it('clamp parsed params', () => {\n      const params = new Params();\n      parse(params, '2147483648');\n      assert.deepEqual(params.toArray(), [0x7FFFFFFF]);\n    });\n    it('clamp parsed subparams', () => {\n      const params = new Params();\n      parse(params, ':2147483648');\n      assert.deepEqual(params.toArray(), [0, [0x7FFFFFFF]]);\n    });\n  });\n  describe('issue 2389', () => {\n    it('should cancel subdigits if beyond params limit', () => {\n      const params = new Params();\n      parse(params, ';;;;;;;;;10;;;;;;;;;;20;;;;;;;;;;30;31;32;33;34;35::::::::');\n      assert.deepEqual(params.toArray(), [\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 10,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 20,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 31, 32]);\n    });\n    it('should carry forward isSub state', () => {\n      const params = new Params();\n      parse(params, ['1:22:33', '44']);\n      assert.deepEqual(params.toArray(), [1, [22, 3344]]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/parser/Params.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { IParams, ParamsArray } from 'common/parser/Types';\n\n// max value supported for a single param/subparam (clamped to positive int32 range)\nconst MAX_VALUE = 0x7FFFFFFF;\n// max allowed subparams for a single sequence (hardcoded limitation)\nconst MAX_SUBPARAMS = 256;\n\n/**\n * Params storage class.\n * This type is used by the parser to accumulate sequence parameters and sub parameters\n * and transmit them to the input handler actions.\n *\n * NOTES:\n *  - params object for action handlers is borrowed, use `.toArray` or `.clone` to get a copy\n *  - never read beyond `params.length - 1` (likely to contain arbitrary data)\n *  - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params\n *  - hardcoded limitations:\n *    - max. value for a single (sub) param is 2^31 - 1 (greater values are clamped to that)\n *    - max. 256 sub params possible\n *    - negative values are not allowed beside -1 (placeholder for default value)\n *\n * About ZDM (Zero Default Mode):\n * ZDM is not orchestrated by this class. If the parser is in ZDM,\n * it should add 0 for empty params, otherwise -1. This does not apply\n * to subparams, empty subparams should always be added with -1.\n */\nexport class Params implements IParams {\n  // params store and length\n  public params: Int32Array;\n  public length: number;\n\n  // sub params store and length\n  protected _subParams: Int32Array;\n  protected _subParamsLength: number;\n\n  // sub params offsets from param: param idx --> [start, end] offset\n  private _subParamsIdx: Uint16Array;\n  private _rejectDigits: boolean;\n  private _rejectSubDigits: boolean;\n  private _digitIsSub: boolean;\n\n  /**\n   * Create a `Params` type from JS array representation.\n   */\n  public static fromArray(values: ParamsArray): Params {\n    const params = new Params();\n    if (!values.length) {\n      return params;\n    }\n    // skip leading sub params\n    for (let i = (Array.isArray(values[0])) ? 1 : 0; i < values.length; ++i) {\n      const value = values[i];\n      if (Array.isArray(value)) {\n        for (let k = 0; k < value.length; ++k) {\n          params.addSubParam(value[k]);\n        }\n      } else {\n        params.addParam(value);\n      }\n    }\n    return params;\n  }\n\n  /**\n   * @param maxLength max length of storable parameters\n   * @param maxSubParamsLength max length of storable sub parameters\n   */\n  constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) {\n    if (maxSubParamsLength > MAX_SUBPARAMS) {\n      throw new Error('maxSubParamsLength must not be greater than 256');\n    }\n    this.params = new Int32Array(maxLength);\n    this.length = 0;\n    this._subParams = new Int32Array(maxSubParamsLength);\n    this._subParamsLength = 0;\n    this._subParamsIdx = new Uint16Array(maxLength);\n    this._rejectDigits = false;\n    this._rejectSubDigits = false;\n    this._digitIsSub = false;\n  }\n\n  /**\n   * Clone object.\n   */\n  public clone(): Params {\n    const newParams = new Params(this.maxLength, this.maxSubParamsLength);\n    newParams.params.set(this.params);\n    newParams.length = this.length;\n    newParams._subParams.set(this._subParams);\n    newParams._subParamsLength = this._subParamsLength;\n    newParams._subParamsIdx.set(this._subParamsIdx);\n    newParams._rejectDigits = this._rejectDigits;\n    newParams._rejectSubDigits = this._rejectSubDigits;\n    newParams._digitIsSub = this._digitIsSub;\n    return newParams;\n  }\n\n  /**\n   * Get a JS array representation of the current parameters and sub parameters.\n   * The array is structured as follows:\n   *    sequence: \"1;2:3:4;5::6\"\n   *    array   : [1, 2, [3, 4], 5, [-1, 6]]\n   */\n  public toArray(): ParamsArray {\n    const res: ParamsArray = [];\n    for (let i = 0; i < this.length; ++i) {\n      res.push(this.params[i]);\n      const start = this._subParamsIdx[i] >> 8;\n      const end = this._subParamsIdx[i] & 0xFF;\n      if (end - start > 0) {\n        res.push(Array.prototype.slice.call(this._subParams, start, end));\n      }\n    }\n    return res;\n  }\n\n  /**\n   * Reset to initial empty state.\n   */\n  public reset(): void {\n    this.length = 0;\n    this._subParamsLength = 0;\n    this._rejectDigits = false;\n    this._rejectSubDigits = false;\n    this._digitIsSub = false;\n  }\n\n  /**\n   * Add a parameter value.\n   * `Params` only stores up to `maxLength` parameters, any later\n   * parameter will be ignored.\n   * Note: VT devices only stored up to 16 values, xterm seems to\n   * store up to 30.\n   */\n  public addParam(value: number): void {\n    this._digitIsSub = false;\n    if (this.length >= this.maxLength) {\n      this._rejectDigits = true;\n      return;\n    }\n    if (value < -1) {\n      throw new Error('values lesser than -1 are not allowed');\n    }\n    this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength;\n    this.params[this.length++] = value > MAX_VALUE ? MAX_VALUE : value;\n  }\n\n  /**\n   * Add a sub parameter value.\n   * The sub parameter is automatically associated with the last parameter value.\n   * Thus it is not possible to add a subparameter without any parameter added yet.\n   * `Params` only stores up to `subParamsLength` sub parameters, any later\n   * sub parameter will be ignored.\n   */\n  public addSubParam(value: number): void {\n    this._digitIsSub = true;\n    if (!this.length) {\n      return;\n    }\n    if (this._rejectDigits || this._subParamsLength >= this.maxSubParamsLength) {\n      this._rejectSubDigits = true;\n      return;\n    }\n    if (value < -1) {\n      throw new Error('values lesser than -1 are not allowed');\n    }\n    this._subParams[this._subParamsLength++] = value > MAX_VALUE ? MAX_VALUE : value;\n    this._subParamsIdx[this.length - 1]++;\n  }\n\n  /**\n   * Whether parameter at index `idx` has sub parameters.\n   */\n  public hasSubParams(idx: number): boolean {\n    return ((this._subParamsIdx[idx] & 0xFF) - (this._subParamsIdx[idx] >> 8) > 0);\n  }\n\n  /**\n   * Return sub parameters for parameter at index `idx`.\n   * Note: The values are borrowed, thus you need to copy\n   * the values if you need to hold them in nonlocal scope.\n   */\n  public getSubParams(idx: number): Int32Array | null {\n    const start = this._subParamsIdx[idx] >> 8;\n    const end = this._subParamsIdx[idx] & 0xFF;\n    if (end - start > 0) {\n      return this._subParams.subarray(start, end);\n    }\n    return null;\n  }\n\n  /**\n   * Return all sub parameters as {idx: subparams} mapping.\n   * Note: The values are not borrowed.\n   */\n  public getSubParamsAll(): {[idx: number]: Int32Array} {\n    const result: {[idx: number]: Int32Array} = {};\n    for (let i = 0; i < this.length; ++i) {\n      const start = this._subParamsIdx[i] >> 8;\n      const end = this._subParamsIdx[i] & 0xFF;\n      if (end - start > 0) {\n        result[i] = this._subParams.slice(start, end);\n      }\n    }\n    return result;\n  }\n\n  /**\n   * Add a single digit value to current parameter.\n   * This is used by the parser to account digits on a char by char basis.\n   */\n  public addDigit(value: number): void {\n    let length;\n    if (this._rejectDigits\n      || !(length = this._digitIsSub ? this._subParamsLength : this.length)\n      || (this._digitIsSub && this._rejectSubDigits)\n    ) {\n      return;\n    }\n\n    const store = this._digitIsSub ? this._subParams : this.params;\n    const cur = store[length - 1];\n    store[length - 1] = ~cur ? Math.min(cur * 10 + value, MAX_VALUE) : value;\n  }\n}\n"
  },
  {
    "path": "src/common/parser/Types.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from 'common/Types';\nimport { ParserState } from 'common/parser/Constants';\n\n\n/** sequence params serialized to js arrays */\nexport type ParamsArray = (number | number[])[];\n\n/** Params constructor type. */\nexport interface IParamsConstructor {\n  new(maxLength: number, maxSubParamsLength: number): IParams;\n\n  /** create params from ParamsArray */\n  fromArray(values: ParamsArray): IParams;\n}\n\n/** Interface of Params storage class. */\nexport interface IParams {\n  /** from ctor */\n  maxLength: number;\n  maxSubParamsLength: number;\n\n  /** param values and its length */\n  params: Int32Array;\n  length: number;\n\n  /** methods */\n  clone(): IParams;\n  toArray(): ParamsArray;\n  reset(): void;\n  addParam(value: number): void;\n  addSubParam(value: number): void;\n  hasSubParams(idx: number): boolean;\n  getSubParams(idx: number): Int32Array | null;\n  getSubParamsAll(): {[idx: number]: Int32Array};\n}\n\n/**\n * Internal state of EscapeSequenceParser.\n * Used as argument of the error handler to allow\n * introspection at runtime on parse errors.\n * Return it with altered values to recover from\n * faulty states (not yet supported).\n * Set `abort` to `true` to abort the current parsing.\n */\nexport interface IParsingState {\n  // position in parse string\n  position: number;\n  // actual character code\n  code: number;\n  // current parser state\n  currentState: ParserState;\n  // collect buffer with intermediate characters\n  collect: number;\n  // params buffer\n  params: IParams;\n  // should abort (default: false)\n  abort: boolean;\n}\n\n/**\n * Command handler interfaces.\n */\n\n/**\n * CSI handler types.\n * Note: `params` is borrowed.\n */\nexport type CsiHandlerType = (params: IParams) => boolean | Promise<boolean>;\nexport type CsiFallbackHandlerType = (ident: number, params: IParams) => void;\n\n/**\n * DCS handler types.\n */\nexport interface IDcsHandler {\n  /**\n   * Called when a DCS command starts.\n   * Prepare needed data structures here.\n   * Note: `params` is borrowed.\n   */\n  hook(params: IParams): void;\n  /**\n   * Incoming payload chunk.\n   * Note: `params` is borrowed.\n   */\n  put(data: Uint32Array, start: number, end: number): void;\n  /**\n   * End of DCS command. `success` indicates whether the\n   * command finished normally or got aborted, thus final\n   * execution of the command should depend on `success`.\n   * To save memory also cleanup data structures here.\n   */\n  unhook(success: boolean): boolean | Promise<boolean>;\n}\nexport type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;\n\n/**\n * ESC handler types.\n */\nexport type EscHandlerType = () => boolean | Promise<boolean>;\nexport type EscFallbackHandlerType = (identifier: number) => void;\n\n/**\n * EXECUTE handler types.\n */\nexport type ExecuteHandlerType = () => boolean;\nexport type ExecuteFallbackHandlerType = (ident: number) => void;\n\n/**\n * OSC handler types.\n */\nexport interface IOscHandler {\n  /**\n   * Announces start of this OSC command.\n   * Prepare needed data structures here.\n   */\n  start(): void;\n  /**\n   * Incoming data chunk.\n   * Note: Data is borrowed.\n   */\n  put(data: Uint32Array, start: number, end: number): void;\n  /**\n   * End of OSC command. `success` indicates whether the\n   * command finished normally or got aborted, thus final\n   * execution of the command should depend on `success`.\n   * To save memory also cleanup data structures here.\n   */\n  end(success: boolean): boolean | Promise<boolean>;\n}\nexport type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;\n\n/**\n * APC handler types.\n */\nexport interface IApcHandler {\n  /**\n   * Announces start of this APC command.\n   * Prepare needed data structures here.\n   */\n  start(): void;\n  /**\n   * Incoming data chunk.\n   */\n  put(data: Uint32Array, start: number, end: number): void;\n  /**\n   * End of APC command. `success` indicates whether the\n   * command finished normally or got aborted, thus final\n   * execution of the command should depend on `success`.\n   * To save memory also cleanup data structures here.\n   */\n  end(success: boolean): boolean | Promise<boolean>;\n}\nexport type ApcFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;\n\n/**\n * PRINT handler types.\n */\nexport type PrintHandlerType = (data: Uint32Array, start: number, end: number) => void;\nexport type PrintFallbackHandlerType = PrintHandlerType;\n\n\n/**\n * EscapeSequenceParser interface.\n */\nexport interface IEscapeSequenceParser extends IDisposable {\n  /**\n   * Preceding grapheme-join-state.\n   * Used for joining grapheme clusters across calls to `print`.\n   * Also used by REP to check if repeating a character is allowed.\n   * It gets reset by the parser for any valid sequence besides text.\n   */\n  precedingJoinState: number; // More specifically: UnicodeJoinProperties\n\n  /**\n   * Reset the parser to its initial state (handlers are kept).\n   */\n  reset(): void;\n\n  /**\n   * Parse UTF32 codepoints in `data` up to `length`.\n   * @param data The data to parse.\n   */\n  parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean>;\n\n  /**\n   * Get string from numercial function identifier `ident`.\n   * Useful in fallback handlers which expose the low level\n   * numcerical function identifier for debugging purposes.\n   * Note: A full back translation to `IFunctionIdentifier`\n   * is not implemented.\n   */\n  identToString(ident: number): string;\n\n  setPrintHandler(handler: PrintHandlerType): void;\n  clearPrintHandler(): void;\n\n  registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable;\n  clearEscHandler(id: IFunctionIdentifier): void;\n  setEscHandlerFallback(handler: EscFallbackHandlerType): void;\n\n  setExecuteHandler(flag: string, handler: ExecuteHandlerType): void;\n  clearExecuteHandler(flag: string): void;\n  setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void;\n\n  registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable;\n  clearCsiHandler(id: IFunctionIdentifier): void;\n  setCsiHandlerFallback(callback: CsiFallbackHandlerType): void;\n\n  registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable;\n  clearDcsHandler(id: IFunctionIdentifier): void;\n  setDcsHandlerFallback(handler: DcsFallbackHandlerType): void;\n\n  registerOscHandler(ident: number, handler: IOscHandler): IDisposable;\n  clearOscHandler(ident: number): void;\n  setOscHandlerFallback(handler: OscFallbackHandlerType): void;\n\n  registerApcHandler(ident: number, handler: IApcHandler): IDisposable;\n  clearApcHandler(ident: number): void;\n  setApcHandlerFallback(handler: ApcFallbackHandlerType): void;\n\n  setErrorHandler(handler: (state: IParsingState) => IParsingState): void;\n  clearErrorHandler(): void;\n}\n\n/**\n * Subparser interfaces.\n * The subparsers are instantiated in `EscapeSequenceParser` and\n * called during `EscapeSequenceParser.parse`.\n */\nexport interface ISubParser<T, U> extends IDisposable {\n  reset(): void;\n  registerHandler(ident: number, handler: T): IDisposable;\n  clearHandler(ident: number): void;\n  setHandlerFallback(handler: U): void;\n  put(data: Uint32Array, start: number, end: number): void;\n}\n\nexport interface IOscParser extends ISubParser<IOscHandler, OscFallbackHandlerType> {\n  start(): void;\n  end(success: boolean, promiseResult?: boolean): void | Promise<boolean>;\n}\n\nexport interface IDcsParser extends ISubParser<IDcsHandler, DcsFallbackHandlerType> {\n  hook(ident: number, params: IParams): void;\n  unhook(success: boolean, promiseResult?: boolean): void | Promise<boolean>;\n}\n\nexport interface IApcParser extends ISubParser<IApcHandler, ApcFallbackHandlerType> {\n  start(): void;\n  end(success: boolean, promiseResult?: boolean): void | Promise<boolean>;\n}\n\n/**\n * Interface to denote a specific ESC, CSI or DCS handler slot.\n * The values are used to create an integer respresentation during handler\n * regristation before passed to the subparsers as `ident`.\n * The integer translation is made to allow a faster handler access\n * in `EscapeSequenceParser.parse`.\n */\nexport interface IFunctionIdentifier {\n  prefix?: string;\n  intermediates?: string;\n  final: string;\n}\n\nexport interface IHandlerCollection<T> {\n  [key: string]: T[];\n}\n\n/**\n * Types for async parser support.\n */\n\n// type of saved stack state in parser\nexport const enum ParserStackType {\n  NONE = 0,\n  FAIL,\n  RESET,\n  CSI,\n  ESC,\n  OSC,\n  DCS,\n  APC\n}\n\n// aggregate of resumable handler lists\nexport type ResumableHandlersType = CsiHandlerType[] | EscHandlerType[];\n\n// saved stack state of the parser\nexport interface IParserStackState {\n  state: ParserStackType;\n  handlers: ResumableHandlersType;\n  handlerPos: number;\n  transition: number;\n  chunkPos: number;\n}\n\n// saved stack state of subparser (OSC and DCS)\nexport interface ISubParserStackState {\n  paused: boolean;\n  loopPosition: number;\n  fallThrough: boolean;\n}\n"
  },
  {
    "path": "src/common/public/AddonManager.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { AddonManager, ILoadedAddon } from './AddonManager';\nimport { ITerminalAddon } from '@xterm/xterm';\n\nclass TestAddonManager extends AddonManager {\n  public get addons(): ILoadedAddon[] {\n    return this._addons;\n  }\n}\n\ndescribe('AddonManager', () => {\n  let manager: TestAddonManager;\n\n  beforeEach(() => {\n    manager = new TestAddonManager();\n  });\n\n  describe('loadAddon', () => {\n    it('should call addon constructor', () => {\n      let called = false;\n      class Addon implements ITerminalAddon {\n        public activate(terminal: any): void {\n          assert.equal(terminal, 'foo', 'The first constructor arg should be Terminal');\n          called = true;\n        }\n        public dispose(): void { }\n      }\n      manager.loadAddon('foo' as any, new Addon());\n      assert.equal(called, true);\n    });\n  });\n\n  describe('dispose', () => {\n    it('should dispose all loaded addons', () => {\n      let called = 0;\n      class Addon implements ITerminalAddon {\n        public activate(): void {}\n        public dispose(): void { called++; }\n      }\n      manager.loadAddon(null!, new Addon());\n      manager.loadAddon(null!, new Addon());\n      manager.loadAddon(null!, new Addon());\n      assert.equal(manager.addons.length, 3);\n      manager.dispose();\n      assert.equal(called, 3);\n      assert.equal(manager.addons.length, 0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/public/AddonManager.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminalAddon, IDisposable, Terminal } from '@xterm/xterm';\n\nexport interface ILoadedAddon {\n  instance: ITerminalAddon;\n  dispose: () => void;\n  isDisposed: boolean;\n}\n\nexport class AddonManager implements IDisposable {\n  protected _addons: ILoadedAddon[] = [];\n\n  public dispose(): void {\n    for (let i = this._addons.length - 1; i >= 0; i--) {\n      this._addons[i].instance.dispose();\n    }\n  }\n\n  public loadAddon(terminal: Terminal, instance: ITerminalAddon): void {\n    const loadedAddon: ILoadedAddon = {\n      instance,\n      dispose: instance.dispose,\n      isDisposed: false\n    };\n    this._addons.push(loadedAddon);\n    instance.dispose = () => this._wrappedAddonDispose(loadedAddon);\n    instance.activate(terminal as any);\n  }\n\n  private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void {\n    if (loadedAddon.isDisposed) {\n      // Do nothing if already disposed\n      return;\n    }\n    let index = -1;\n    for (let i = 0; i < this._addons.length; i++) {\n      if (this._addons[i] === loadedAddon) {\n        index = i;\n        break;\n      }\n    }\n    if (index === -1) {\n      throw new Error('Could not dispose an addon that has not been loaded');\n    }\n    loadedAddon.isDisposed = true;\n    loadedAddon.dispose.apply(loadedAddon.instance);\n    this._addons.splice(index, 1);\n  }\n}\n"
  },
  {
    "path": "src/common/public/BufferApiView.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from '@xterm/xterm';\nimport { IBuffer } from 'common/buffer/Types';\nimport { BufferLineApiView } from 'common/public/BufferLineApiView';\nimport { CellData } from 'common/buffer/CellData';\n\nexport class BufferApiView implements IBufferApi {\n  constructor(\n    private _buffer: IBuffer,\n    public readonly type: 'normal' | 'alternate'\n  ) { }\n\n  public init(buffer: IBuffer): BufferApiView {\n    this._buffer = buffer;\n    return this;\n  }\n\n  public get cursorY(): number { return this._buffer.y; }\n  public get cursorX(): number { return this._buffer.x; }\n  public get viewportY(): number { return this._buffer.ydisp; }\n  public get baseY(): number { return this._buffer.ybase; }\n  public get length(): number { return this._buffer.lines.length; }\n  public getLine(y: number): IBufferLineApi | undefined {\n    const line = this._buffer.lines.get(y);\n    if (!line) {\n      return undefined;\n    }\n    return new BufferLineApiView(line);\n  }\n  public getNullCell(): IBufferCellApi { return new CellData(); }\n}\n"
  },
  {
    "path": "src/common/public/BufferLineApiView.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CellData } from 'common/buffer/CellData';\nimport { IBufferLine, ICellData } from 'common/Types';\nimport { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from '@xterm/xterm';\n\nexport class BufferLineApiView implements IBufferLineApi {\n  constructor(private _line: IBufferLine) { }\n\n  public get isWrapped(): boolean { return this._line.isWrapped; }\n  public get length(): number { return this._line.length; }\n  public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined {\n    if (x < 0 || x >= this._line.length) {\n      return undefined;\n    }\n\n    if (cell) {\n      this._line.loadCell(x, cell as unknown as ICellData);\n      return cell;\n    }\n    return this._line.loadCell(x, new CellData()) as unknown as IBufferCellApi;\n  }\n  public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {\n    return this._line.translateToString(trimRight, startColumn, endColumn);\n  }\n}\n"
  },
  {
    "path": "src/common/public/BufferNamespaceApi.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from '@xterm/xterm';\nimport { BufferApiView } from 'common/public/BufferApiView';\nimport { ICoreTerminal } from 'common/Types';\nimport { Disposable } from 'common/Lifecycle';\nimport { Emitter } from 'common/Event';\n\nexport class BufferNamespaceApi extends Disposable implements IBufferNamespaceApi {\n  private _normal: BufferApiView;\n  private _alternate: BufferApiView;\n\n  private readonly _onBufferChange = this._register(new Emitter<IBufferApi>());\n  public readonly onBufferChange = this._onBufferChange.event;\n\n  constructor(private _core: ICoreTerminal) {\n    super();\n    this._normal = new BufferApiView(this._core.buffers.normal, 'normal');\n    this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate');\n    this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));\n  }\n  public get active(): IBufferApi {\n    if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; }\n    if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; }\n    throw new Error('Active buffer is neither normal nor alternate');\n  }\n  public get normal(): IBufferApi {\n    return this._normal.init(this._core.buffers.normal);\n  }\n  public get alternate(): IBufferApi {\n    return this._alternate.init(this._core.buffers.alt);\n  }\n}\n"
  },
  {
    "path": "src/common/public/ParserApi.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IParams } from 'common/parser/Types';\nimport { IDisposable, IFunctionIdentifier, IParser } from '@xterm/xterm';\nimport { ICoreTerminal } from 'common/Types';\n\nexport class ParserApi implements IParser {\n  constructor(private _core: ICoreTerminal) { }\n\n  public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {\n    return this._core.registerCsiHandler(id, (params: IParams) => callback(params.toArray()));\n  }\n  public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {\n    return this.registerCsiHandler(id, callback);\n  }\n  public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {\n    return this._core.registerDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));\n  }\n  public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {\n    return this.registerDcsHandler(id, callback);\n  }\n  public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable {\n    return this._core.registerEscHandler(id, handler);\n  }\n  public addEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable {\n    return this.registerEscHandler(id, handler);\n  }\n  public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this._core.registerOscHandler(ident, callback);\n  }\n  public addOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this.registerOscHandler(ident, callback);\n  }\n  public registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {\n    return this._core.registerApcHandler(ident, callback);\n  }\n}\n"
  },
  {
    "path": "src/common/public/UnicodeApi.ts",
    "content": "/**\n * Copyright (c) 2021 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICoreTerminal } from 'common/Types';\nimport { IUnicodeHandling, IUnicodeVersionProvider } from '@xterm/xterm';\n\nexport class UnicodeApi implements IUnicodeHandling {\n  constructor(private _core: ICoreTerminal) { }\n\n  public register(provider: IUnicodeVersionProvider): void {\n    this._core.unicodeService.register(provider);\n  }\n\n  public get versions(): string[] {\n    return this._core.unicodeService.versions;\n  }\n\n  public get activeVersion(): string {\n    return this._core.unicodeService.activeVersion;\n  }\n\n  public set activeVersion(version: string) {\n    this._core.unicodeService.activeVersion = version;\n  }\n}\n"
  },
  {
    "path": "src/common/services/BufferService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Disposable } from 'common/Lifecycle';\nimport { IAttributeData, IBufferLine } from 'common/Types';\nimport { BufferSet } from 'common/buffer/BufferSet';\nimport { IBuffer, IBufferSet } from 'common/buffer/Types';\nimport { IBufferService, ILogService, IOptionsService, type IBufferResizeEvent } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\nexport const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars\nexport const MINIMUM_ROWS = 1;\n\nexport class BufferService extends Disposable implements IBufferService {\n  public serviceBrand: any;\n\n  public cols: number;\n  public rows: number;\n  public buffers: IBufferSet;\n  /** Whether the user is scrolling (locks the scroll position) */\n  public isUserScrolling: boolean = false;\n\n  private readonly _onResize = this._register(new Emitter<IBufferResizeEvent>());\n  public readonly onResize = this._onResize.event;\n  private readonly _onScroll = this._register(new Emitter<number>());\n  public readonly onScroll = this._onScroll.event;\n\n  public get buffer(): IBuffer { return this.buffers.active; }\n\n  /** An IBufferline to clone/copy from for new blank lines */\n  private _cachedBlankLine: IBufferLine | undefined;\n\n  constructor(\n    @IOptionsService optionsService: IOptionsService,\n    @ILogService logService: ILogService\n  ) {\n    super();\n    this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS);\n    this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS);\n    this.buffers = this._register(new BufferSet(optionsService, this, logService));\n    this._register(this.buffers.onBufferActivate(e => {\n      this._onScroll.fire(e.activeBuffer.ydisp);\n    }));\n  }\n\n  public resize(cols: number, rows: number): void {\n    const colsChanged = this.cols !== cols;\n    const rowsChanged = this.rows !== rows;\n    this.cols = cols;\n    this.rows = rows;\n    this.buffers.resize(cols, rows);\n    this._onResize.fire({ cols, rows, colsChanged, rowsChanged });\n  }\n\n  public reset(): void {\n    this.buffers.reset();\n    this.isUserScrolling = false;\n  }\n\n  /**\n   * Scroll the terminal down 1 row, creating a blank line.\n   * @param eraseAttr The attribute data to use the for blank line.\n   * @param isWrapped Whether the new line is wrapped from the previous line.\n   */\n  public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {\n    const buffer = this.buffer;\n\n    let newLine: IBufferLine | undefined;\n    newLine = this._cachedBlankLine;\n    if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {\n      newLine = buffer.getBlankLine(eraseAttr, isWrapped);\n      this._cachedBlankLine = newLine;\n    }\n    newLine.isWrapped = isWrapped;\n\n    const topRow = buffer.ybase + buffer.scrollTop;\n    const bottomRow = buffer.ybase + buffer.scrollBottom;\n\n    if (buffer.scrollTop === 0) {\n      // Determine whether the buffer is going to be trimmed after insertion.\n      const willBufferBeTrimmed = buffer.lines.isFull;\n\n      // Insert the line using the fastest method\n      if (bottomRow === buffer.lines.length - 1) {\n        if (willBufferBeTrimmed) {\n          buffer.lines.recycle().copyFrom(newLine);\n        } else {\n          buffer.lines.push(newLine.clone());\n        }\n      } else {\n        buffer.lines.splice(bottomRow + 1, 0, newLine.clone());\n      }\n\n      // Only adjust ybase and ydisp when the buffer is not trimmed\n      if (!willBufferBeTrimmed) {\n        buffer.ybase++;\n        // Only scroll the ydisp with ybase if the user has not scrolled up\n        if (!this.isUserScrolling) {\n          buffer.ydisp++;\n        }\n      } else {\n        // When the buffer is full and the user has scrolled up, keep the text\n        // stable unless ydisp is right at the top\n        if (this.isUserScrolling) {\n          buffer.ydisp = Math.max(buffer.ydisp - 1, 0);\n        }\n      }\n    } else {\n      // scrollTop is non-zero which means no line will be going to the\n      // scrollback, instead we can just shift them in-place.\n      const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */;\n      buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);\n      buffer.lines.set(bottomRow, newLine.clone());\n    }\n\n    // Move the viewport to the bottom of the buffer unless the user is\n    // scrolling.\n    if (!this.isUserScrolling) {\n      buffer.ydisp = buffer.ybase;\n    }\n\n    this._onScroll.fire(buffer.ydisp);\n  }\n\n  /**\n   * Scroll the display of the terminal\n   * @param disp The number of lines to scroll down (negative scroll up).\n   * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used\n   * to avoid unwanted events being handled by the viewport when the event was triggered from the\n   * viewport originally.\n   */\n  public scrollLines(disp: number, suppressScrollEvent?: boolean): void {\n    const buffer = this.buffer;\n    if (disp < 0) {\n      if (buffer.ydisp === 0) {\n        return;\n      }\n      this.isUserScrolling = true;\n    } else if (disp + buffer.ydisp >= buffer.ybase) {\n      this.isUserScrolling = false;\n    }\n\n    const oldYdisp = buffer.ydisp;\n    buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0);\n\n    // No change occurred, don't trigger scroll/refresh\n    if (oldYdisp === buffer.ydisp) {\n      return;\n    }\n\n    if (!suppressScrollEvent) {\n      this._onScroll.fire(buffer.ydisp);\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/services/CharsetService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharsetService } from 'common/services/Services';\nimport { ICharset } from 'common/Types';\n\nexport class CharsetService implements ICharsetService {\n  public serviceBrand: any;\n\n  public charset: ICharset | undefined;\n  public glevel: number = 0;\n\n  private _charsets: (ICharset | undefined)[] = [];\n\n  public get charsets(): (ICharset | undefined)[] {\n    return this._charsets;\n  }\n\n  public reset(): void {\n    this.charset = undefined;\n    this._charsets = [];\n    this.glevel = 0;\n  }\n\n  public setgLevel(g: number): void {\n    this.glevel = g;\n    this.charset = this._charsets[g];\n  }\n\n  public setgCharset(g: number, charset: ICharset | undefined): void {\n    this._charsets[g] = charset;\n    if (this.glevel === g) {\n      this.charset = charset;\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/services/CoreService.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICoreService } from 'common/services/Services';\nimport { CoreService } from 'common/services/CoreService';\nimport { MockBufferService, MockLogService, MockOptionsService } from 'common/TestUtils.test';\nimport { assert } from 'chai';\n\ndescribe('CoreService', () => {\n  let coreService: ICoreService;\n\n  beforeEach(() => {\n    coreService = new CoreService(\n      new MockBufferService(80, 30),\n      new MockLogService(),\n      new MockOptionsService());\n  });\n\n  describe('isCursorInitialized', () => {\n    it('should be false by default', () => {\n      assert.equal(coreService.isCursorInitialized, false);\n    });\n    it('should be true when showCursorImmediately is true', () => {\n      const coreServiceWithOption = new CoreService(\n        new MockBufferService(80, 30),\n        new MockLogService(),\n        new MockOptionsService({ showCursorImmediately: true }));\n      assert.equal(coreServiceWithOption.isCursorInitialized, true);\n    });\n  });\n\n  describe('reset', () => {\n    it('should not affect isCursorInitialized', () => {\n      coreService.isCursorInitialized = true;\n      coreService.reset();\n      assert.equal(coreService.isCursorInitialized, true);\n      coreService.isCursorInitialized = false;\n      coreService.reset();\n      assert.equal(coreService.isCursorInitialized, false);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/services/CoreService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { clone } from 'common/Clone';\nimport { Disposable } from 'common/Lifecycle';\nimport { IDecPrivateModes, IKittyKeyboardState, IModes } from 'common/Types';\nimport { IBufferService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\nconst DEFAULT_MODES: IModes = Object.freeze({\n  insertMode: false\n});\n\nconst DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({\n  applicationCursorKeys: false,\n  applicationKeypad: false,\n  bracketedPasteMode: false,\n  colorSchemeUpdates: false,\n  cursorBlink: undefined,\n  cursorStyle: undefined,\n  origin: false,\n  reverseWraparound: false,\n  sendFocus: false,\n  synchronizedOutput: false,\n  win32InputMode: false,\n  wraparound: true // defaults: xterm - true, vt100 - false\n});\n\nconst DEFAULT_KITTY_KEYBOARD_STATE = (): IKittyKeyboardState => ({\n  flags: 0,\n  mainFlags: 0,\n  altFlags: 0,\n  mainStack: [],\n  altStack: []\n});\n\nexport class CoreService extends Disposable implements ICoreService {\n  public serviceBrand: any;\n\n  public isCursorInitialized: boolean;\n  public isCursorHidden: boolean = false;\n  public modes: IModes;\n  public decPrivateModes: IDecPrivateModes;\n  public kittyKeyboard: IKittyKeyboardState;\n\n  private readonly _onData = this._register(new Emitter<string>());\n  public readonly onData = this._onData.event;\n  private readonly _onUserInput = this._register(new Emitter<void>());\n  public readonly onUserInput = this._onUserInput.event;\n  private readonly _onBinary = this._register(new Emitter<string>());\n  public readonly onBinary = this._onBinary.event;\n  private readonly _onRequestScrollToBottom = this._register(new Emitter<void>());\n  public readonly onRequestScrollToBottom = this._onRequestScrollToBottom.event;\n\n  constructor(\n    @IBufferService private readonly _bufferService: IBufferService,\n    @ILogService private readonly _logService: ILogService,\n    @IOptionsService private readonly _optionsService: IOptionsService\n  ) {\n    super();\n    this.isCursorInitialized = _optionsService.rawOptions.showCursorImmediately ?? false;\n    this.modes = clone(DEFAULT_MODES);\n    this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);\n    this.kittyKeyboard = DEFAULT_KITTY_KEYBOARD_STATE();\n  }\n\n  public reset(): void {\n    this.modes = clone(DEFAULT_MODES);\n    this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);\n    this.kittyKeyboard = DEFAULT_KITTY_KEYBOARD_STATE();\n  }\n\n  public triggerDataEvent(data: string, wasUserInput: boolean = false): void {\n    // Prevents all events to pty process if stdin is disabled\n    if (this._optionsService.rawOptions.disableStdin) {\n      return;\n    }\n\n    // Input is being sent to the terminal, the terminal should focus the prompt.\n    const buffer = this._bufferService.buffer;\n    if (wasUserInput && this._optionsService.rawOptions.scrollOnUserInput && buffer.ybase !== buffer.ydisp) {\n      this._onRequestScrollToBottom.fire();\n    }\n\n    // Fire onUserInput so listeners can react as well (eg. clear selection)\n    if (wasUserInput) {\n      this._onUserInput.fire();\n    }\n\n    // Fire onData API\n    this._logService.debug(`sending data \"${data}\"`);\n    this._logService.trace(`sending data (codes)`, () => data.split('').map(e => e.charCodeAt(0)));\n    this._onData.fire(data);\n  }\n\n  public triggerBinaryEvent(data: string): void {\n    if (this._optionsService.rawOptions.disableStdin) {\n      return;\n    }\n    this._logService.debug(`sending binary \"${data}\"`);\n    this._logService.trace(`sending binary (codes)`, () => data.split('').map(e => e.charCodeAt(0)));\n    this._onBinary.fire(data);\n  }\n}\n"
  },
  {
    "path": "src/common/services/DecorationService.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { DecorationService } from './DecorationService';\nimport { IMarker } from 'common/Types';\nimport { Disposable } from 'common/Lifecycle';\nimport { Emitter } from 'common/Event';\nimport { MockLogService } from 'common/TestUtils.test';\n\nfunction createFakeMarker(line: number): IMarker {\n  return Object.freeze(new class extends Disposable {\n    public readonly id = 1;\n    public readonly line = line;\n    public readonly isDisposed = false;\n    public readonly onDispose = new Emitter<void>().event;\n  }());\n}\n\nconst fakeMarker: IMarker = createFakeMarker(1);\n\ndescribe('DecorationService', () => {\n  it('should set isDisposed to true after dispose', () => {\n    const service = new DecorationService(new MockLogService());\n    const decoration = service.registerDecoration({\n      marker: fakeMarker\n    });\n    assert.ok(decoration);\n    assert.isFalse(decoration!.isDisposed);\n    decoration!.dispose();\n    assert.isTrue(decoration!.isDisposed);\n  });\n\n  describe('forEachDecorationAtCell', () => {\n    it('should find decoration at its marker line', () => {\n      const service = new DecorationService(new MockLogService());\n      const decoration = service.registerDecoration({\n        marker: createFakeMarker(5),\n        width: 10\n      });\n      assert.ok(decoration);\n\n      const found: typeof decoration[] = [];\n      service.forEachDecorationAtCell(0, 5, undefined, d => found.push(d));\n      assert.strictEqual(found.length, 1);\n    });\n\n    it('should find decoration with height > 1 on subsequent lines', () => {\n      const service = new DecorationService(new MockLogService());\n      const decoration = service.registerDecoration({\n        marker: createFakeMarker(5),\n        width: 10,\n        height: 3\n      });\n      assert.ok(decoration);\n\n      const foundAt5: typeof decoration[] = [];\n      service.forEachDecorationAtCell(0, 5, undefined, d => foundAt5.push(d));\n      assert.strictEqual(foundAt5.length, 1);\n\n      const foundAt6: typeof decoration[] = [];\n      service.forEachDecorationAtCell(0, 6, undefined, d => foundAt6.push(d));\n      assert.strictEqual(foundAt6.length, 1);\n\n      const foundAt7: typeof decoration[] = [];\n      service.forEachDecorationAtCell(0, 7, undefined, d => foundAt7.push(d));\n      assert.strictEqual(foundAt7.length, 1);\n\n      const foundAt8: typeof decoration[] = [];\n      service.forEachDecorationAtCell(0, 8, undefined, d => foundAt8.push(d));\n      assert.strictEqual(foundAt8.length, 0);\n    });\n\n    it('should not find decoration outside its x range', () => {\n      const service = new DecorationService(new MockLogService());\n      const decoration = service.registerDecoration({\n        marker: createFakeMarker(5),\n        x: 5,\n        width: 3,\n        height: 2\n      });\n      assert.ok(decoration);\n\n      const foundAtX4: typeof decoration[] = [];\n      service.forEachDecorationAtCell(4, 5, undefined, d => foundAtX4.push(d));\n      assert.strictEqual(foundAtX4.length, 0);\n\n      const foundAtX5: typeof decoration[] = [];\n      service.forEachDecorationAtCell(5, 5, undefined, d => foundAtX5.push(d));\n      assert.strictEqual(foundAtX5.length, 1);\n\n      const foundAtX7: typeof decoration[] = [];\n      service.forEachDecorationAtCell(7, 6, undefined, d => foundAtX7.push(d));\n      assert.strictEqual(foundAtX7.length, 1);\n\n      const foundAtX8: typeof decoration[] = [];\n      service.forEachDecorationAtCell(8, 5, undefined, d => foundAtX8.push(d));\n      assert.strictEqual(foundAtX8.length, 0);\n    });\n  });\n\n  describe('getDecorationsAtCell', () => {\n    it('should find decoration with height > 1 on subsequent lines', () => {\n      const service = new DecorationService(new MockLogService());\n      const decoration = service.registerDecoration({\n        marker: createFakeMarker(5),\n        width: 10,\n        height: 3\n      });\n      assert.ok(decoration);\n\n      assert.strictEqual([...service.getDecorationsAtCell(0, 5)].length, 1);\n      assert.strictEqual([...service.getDecorationsAtCell(0, 6)].length, 1);\n      assert.strictEqual([...service.getDecorationsAtCell(0, 7)].length, 1);\n      assert.strictEqual([...service.getDecorationsAtCell(0, 8)].length, 0);\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/services/DecorationService.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { css } from 'common/Color';\nimport { Disposable, DisposableStore, toDisposable } from 'common/Lifecycle';\nimport { IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';\nimport { SortedList } from 'common/SortedList';\nimport { IColor } from 'common/Types';\nimport { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm';\nimport { Emitter } from 'common/Event';\n\n// Work variables to avoid garbage collection\nlet $xmin = 0;\nlet $xmax = 0;\nlet $ymin = 0;\nlet $ymax = 0;\n\nexport class DecorationService extends Disposable implements IDecorationService {\n  public serviceBrand: any;\n\n  /**\n   * A list of all decorations, sorted by the marker's line value. This relies on the fact that\n   * while marker line values do change, they should all change by the same amount so this should\n   * never become out of order.\n   */\n  private readonly _decorations: SortedList<IInternalDecoration>;\n\n  private readonly _onDecorationRegistered = this._register(new Emitter<IInternalDecoration>());\n  public readonly onDecorationRegistered = this._onDecorationRegistered.event;\n  private readonly _onDecorationRemoved = this._register(new Emitter<IInternalDecoration>());\n  public readonly onDecorationRemoved = this._onDecorationRemoved.event;\n\n  public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }\n\n  constructor(@ILogService private readonly _logService: ILogService) {\n    super();\n\n    this._decorations = new SortedList(e => e?.marker.line, this._logService);\n\n    this._register(toDisposable(() => this.reset()));\n  }\n\n  public registerDecoration(options: IDecorationOptions): IDecoration | undefined {\n    if (options.marker.isDisposed) {\n      return undefined;\n    }\n    const decoration = new Decoration(options);\n    if (decoration) {\n      const markerDispose = decoration.marker.onDispose(() => decoration.dispose());\n      const listener = decoration.onDispose(() => {\n        listener.dispose();\n        if (decoration) {\n          if (this._decorations.delete(decoration)) {\n            this._onDecorationRemoved.fire(decoration);\n          }\n          markerDispose.dispose();\n        }\n      });\n      this._decorations.insert(decoration);\n      this._onDecorationRegistered.fire(decoration);\n    }\n    return decoration;\n  }\n\n  public reset(): void {\n    for (const d of this._decorations.values()) {\n      d.dispose();\n    }\n    this._decorations.clear();\n  }\n\n  public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> {\n    let xmin = 0;\n    let xmax = 0;\n    let ymin = 0;\n    let ymax = 0;\n    for (const d of this._decorations.values()) {\n      ymin = d.marker.line;\n      ymax = ymin + (d.options.height ?? 1);\n      if (line < ymin || line >= ymax) {\n        continue;\n      }\n      xmin = d.options.x ?? 0;\n      xmax = xmin + (d.options.width ?? 1);\n      if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {\n        yield d;\n      }\n    }\n  }\n\n  public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void {\n    for (const d of this._decorations.values()) {\n      $ymin = d.marker.line;\n      $ymax = $ymin + (d.options.height ?? 1);\n      if (line < $ymin || line >= $ymax) {\n        continue;\n      }\n      $xmin = d.options.x ?? 0;\n      $xmax = $xmin + (d.options.width ?? 1);\n      if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {\n        callback(d);\n      }\n    }\n  }\n}\n\nclass Decoration extends DisposableStore implements IInternalDecoration {\n  public readonly marker: IMarker;\n  public element: HTMLElement | undefined;\n\n  public readonly onRenderEmitter = this.add(new Emitter<HTMLElement>());\n  public readonly onRender = this.onRenderEmitter.event;\n  private readonly _onDispose = this.add(new Emitter<void>());\n  public readonly onDispose = this._onDispose.event;\n\n  private _cachedBg: IColor | undefined | null = null;\n  public get backgroundColorRGB(): IColor | undefined {\n    if (this._cachedBg === null) {\n      if (this.options.backgroundColor) {\n        this._cachedBg = css.toColor(this.options.backgroundColor);\n      } else {\n        this._cachedBg = undefined;\n      }\n    }\n    return this._cachedBg;\n  }\n\n  private _cachedFg: IColor | undefined | null = null;\n  public get foregroundColorRGB(): IColor | undefined {\n    if (this._cachedFg === null) {\n      if (this.options.foregroundColor) {\n        this._cachedFg = css.toColor(this.options.foregroundColor);\n      } else {\n        this._cachedFg = undefined;\n      }\n    }\n    return this._cachedFg;\n  }\n\n  constructor(\n    public readonly options: IDecorationOptions\n  ) {\n    super();\n    this.marker = options.marker;\n    if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {\n      this.options.overviewRulerOptions.position = 'full';\n    }\n  }\n\n  public override dispose(): void {\n    this._onDispose.fire();\n    super.dispose();\n  }\n}\n"
  },
  {
    "path": "src/common/services/InstantiationService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This was heavily inspired from microsoft/vscode's dependency injection system (MIT).\n */\n/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { IInstantiationService, IServiceIdentifier } from 'common/services/Services';\nimport { getServiceDependencies } from 'common/services/ServiceRegistry';\n\nexport class ServiceCollection {\n\n  private _entries = new Map<IServiceIdentifier<any>, any>();\n\n  constructor(...entries: [IServiceIdentifier<any>, any][]) {\n    for (const [id, service] of entries) {\n      this.set(id, service);\n    }\n  }\n\n  public set<T>(id: IServiceIdentifier<T>, instance: T): T {\n    const result = this._entries.get(id);\n    this._entries.set(id, instance);\n    return result;\n  }\n\n  public forEach(callback: (id: IServiceIdentifier<any>, instance: any) => any): void {\n    for (const [key, value] of this._entries.entries()) {\n      callback(key, value);\n    }\n  }\n\n  public has(id: IServiceIdentifier<any>): boolean {\n    return this._entries.has(id);\n  }\n\n  public get<T>(id: IServiceIdentifier<T>): T | undefined {\n    return this._entries.get(id);\n  }\n}\n\nexport class InstantiationService implements IInstantiationService {\n  public serviceBrand: undefined;\n\n  private readonly _services: ServiceCollection = new ServiceCollection();\n\n  constructor() {\n    this._services.set(IInstantiationService, this);\n  }\n\n  public setService<T>(id: IServiceIdentifier<T>, instance: T): void {\n    this._services.set(id, instance);\n  }\n\n  public getService<T>(id: IServiceIdentifier<T>): T | undefined {\n    return this._services.get(id);\n  }\n\n  public createInstance<T>(ctor: any, ...args: any[]): T {\n    const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index);\n\n    const serviceArgs: any[] = [];\n    for (const dependency of serviceDependencies) {\n      const service = this._services.get(dependency.id);\n      if (!service) {\n        throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id._id}.`);\n      }\n      serviceArgs.push(service);\n    }\n\n    const firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;\n\n    // check for argument mismatches, adjust static args if needed\n    if (args.length !== firstServiceArgPos) {\n      throw new Error(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);\n    }\n\n    // now create the instance\n    return new ctor(...[...args, ...serviceArgs]);\n  }\n}\n"
  },
  {
    "path": "src/common/services/LogService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Disposable } from 'common/Lifecycle';\nimport { ILogService, IOptionsService, LogLevelEnum } from 'common/services/Services';\n\ntype LogType = (message?: any, ...optionalParams: any[]) => void;\n\ninterface IConsole {\n  log: LogType;\n  error: LogType;\n  info: LogType;\n  trace: LogType;\n  warn: LogType;\n}\n\n// console is available on both node.js and browser contexts but the common\n// module doesn't depend on them so we need to explicitly declare it.\ndeclare const console: IConsole;\n\nconst optionsKeyToLogLevel: { [key: string]: LogLevelEnum } = {\n  trace: LogLevelEnum.TRACE,\n  debug: LogLevelEnum.DEBUG,\n  info: LogLevelEnum.INFO,\n  warn: LogLevelEnum.WARN,\n  error: LogLevelEnum.ERROR,\n  off: LogLevelEnum.OFF\n};\n\nconst LOG_PREFIX = 'xterm.js: ';\n\nexport class LogService extends Disposable implements ILogService {\n  public serviceBrand: any;\n\n  private _logLevel: LogLevelEnum = LogLevelEnum.OFF;\n  public get logLevel(): LogLevelEnum { return this._logLevel; }\n\n  constructor(\n    @IOptionsService private readonly _optionsService: IOptionsService\n  ) {\n    super();\n    this._updateLogLevel();\n    this._register(this._optionsService.onSpecificOptionChange('logLevel', () => this._updateLogLevel()));\n  }\n\n  private _updateLogLevel(): void {\n    this._logLevel = optionsKeyToLogLevel[this._optionsService.rawOptions.logLevel];\n  }\n\n  private _evalLazyOptionalParams(optionalParams: any[]): void {\n    for (let i = 0; i < optionalParams.length; i++) {\n      if (typeof optionalParams[i] === 'function') {\n        optionalParams[i] = optionalParams[i]();\n      }\n    }\n  }\n\n  private _log(type: LogType, message: string, optionalParams: any[]): void {\n    this._evalLazyOptionalParams(optionalParams);\n    type.call(console, (this._optionsService.options.logger ? '' : LOG_PREFIX) + message, ...optionalParams);\n  }\n\n  public trace(message: string, ...optionalParams: any[]): void {\n    if (this._logLevel <= LogLevelEnum.TRACE) {\n      this._log(this._optionsService.options.logger?.trace.bind(this._optionsService.options.logger) ?? console.log, message, optionalParams);\n    }\n  }\n\n  public debug(message: string, ...optionalParams: any[]): void {\n    if (this._logLevel <= LogLevelEnum.DEBUG) {\n      this._log(this._optionsService.options.logger?.debug.bind(this._optionsService.options.logger) ?? console.log, message, optionalParams);\n    }\n  }\n\n  public info(message: string, ...optionalParams: any[]): void {\n    if (this._logLevel <= LogLevelEnum.INFO) {\n      this._log(this._optionsService.options.logger?.info.bind(this._optionsService.options.logger) ?? console.info, message, optionalParams);\n    }\n  }\n\n  public warn(message: string, ...optionalParams: any[]): void {\n    if (this._logLevel <= LogLevelEnum.WARN) {\n      this._log(this._optionsService.options.logger?.warn.bind(this._optionsService.options.logger) ?? console.warn, message, optionalParams);\n    }\n  }\n\n  public error(message: string, ...optionalParams: any[]): void {\n    if (this._logLevel <= LogLevelEnum.ERROR) {\n      this._log(this._optionsService.options.logger?.error.bind(this._optionsService.options.logger) ?? console.error, message, optionalParams);\n    }\n  }\n}\n"
  },
  {
    "path": "src/common/services/MouseStateService.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { MouseStateService } from 'common/services/MouseStateService';\nimport { assert } from 'chai';\nimport { ICoreMouseEvent, CoreMouseEventType } from 'common/Types';\n\nfunction toBytes(s: string | undefined): number[] {\n  if (!s) {\n    return [];\n  }\n  const res: number[] = [];\n  for (let i = 0; i < s.length; ++i) {\n    res.push(s.charCodeAt(i));\n  }\n  return res;\n}\n\ndescribe('MouseStateService', () => {\n  it('init', () => {\n    const cms = new MouseStateService();\n    assert.equal(cms.activeEncoding, 'DEFAULT');\n    assert.equal(cms.activeProtocol, 'NONE');\n  });\n  it('default protocols - NONE, X10, VT200, DRAG, ANY', () => {\n    const cms = new MouseStateService();\n    assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']);\n  });\n  it('default encodings - DEFAULT, SGR', () => {\n    const cms = new MouseStateService();\n    assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'SGR', 'SGR_PIXELS']);\n  });\n  it('protocol/encoding setter, reset', () => {\n    const cms = new MouseStateService();\n    cms.activeEncoding = 'SGR';\n    cms.activeProtocol = 'ANY';\n    assert.equal(cms.activeEncoding, 'SGR');\n    assert.equal(cms.activeProtocol, 'ANY');\n    cms.reset();\n    assert.equal(cms.activeEncoding, 'DEFAULT');\n    assert.equal(cms.activeProtocol, 'NONE');\n    assert.throws(() => { cms.activeEncoding = 'xyz'; }, 'unknown encoding \"xyz\"');\n    assert.throws(() => { cms.activeProtocol = 'xyz'; }, 'unknown protocol \"xyz\"');\n  });\n  it('addEncoding', () => {\n    const cms = new MouseStateService();\n    cms.addEncoding('XYZ', (e: ICoreMouseEvent) => '');\n    cms.activeEncoding = 'XYZ';\n    assert.equal(cms.activeEncoding, 'XYZ');\n  });\n  it('addProtocol', () => {\n    const cms = new MouseStateService();\n    cms.addProtocol('XYZ', { events: CoreMouseEventType.NONE, restrict: (e: ICoreMouseEvent) => false });\n    cms.activeProtocol = 'XYZ';\n    assert.equal(cms.activeProtocol, 'XYZ');\n  });\n  it('onProtocolChange', () => {\n    const cms = new MouseStateService();\n    const wantedEvents: CoreMouseEventType[] = [];\n    cms.onProtocolChange(events => wantedEvents.push(events));\n    cms.activeProtocol = 'NONE';\n    assert.deepEqual(wantedEvents, [CoreMouseEventType.NONE]);\n    cms.activeProtocol = 'ANY';\n    assert.deepEqual(wantedEvents, [\n      CoreMouseEventType.NONE,\n      CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE\n    ]);\n  });\n  it('restrictMouseEvent/encodeMouseEvent', () => {\n    const cms = new MouseStateService();\n    const event: ICoreMouseEvent = {\n      col: 1,\n      row: 1,\n      x: 0,\n      y: 0,\n      button: 0,\n      action: 1,\n      ctrl: false,\n      alt: false,\n      shift: false\n    };\n    cms.activeProtocol = 'ANY';\n    cms.activeEncoding = 'DEFAULT';\n    assert.equal(cms.restrictMouseEvent(event), true);\n    assert.deepEqual(toBytes(cms.encodeMouseEvent(event)), [0x1b, 0x5b, 0x4d, 0x20, 0x21, 0x21]);\n  });\n});\n"
  },
  {
    "path": "src/common/services/MouseStateService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { IMouseStateService } from 'common/services/Services';\nimport { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';\nimport { Disposable } from 'common/Lifecycle';\nimport { Emitter } from 'common/Event';\n\n/**\n * Supported default protocols.\n */\nconst DEFAULT_PROTOCOLS: { [key: string]: ICoreMouseProtocol } = {\n  /**\n   * NONE\n   * Events: none\n   * Modifiers: none\n   */\n  NONE: {\n    events: CoreMouseEventType.NONE,\n    restrict: () => false\n  },\n  /**\n   * X10\n   * Events: mousedown\n   * Modifiers: none\n   */\n  X10: {\n    events: CoreMouseEventType.DOWN,\n    restrict: (e: ICoreMouseEvent) => {\n      // no wheel, no move, no up\n      if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) {\n        return false;\n      }\n      // no modifiers\n      e.ctrl = false;\n      e.alt = false;\n      e.shift = false;\n      return true;\n    }\n  },\n  /**\n   * VT200\n   * Events: mousedown / mouseup / wheel\n   * Modifiers: all\n   */\n  VT200: {\n    events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL,\n    restrict: (e: ICoreMouseEvent) => {\n      // no move\n      if (e.action === CoreMouseAction.MOVE) {\n        return false;\n      }\n      return true;\n    }\n  },\n  /**\n   * DRAG\n   * Events: mousedown / mouseup / wheel / mousedrag\n   * Modifiers: all\n   */\n  DRAG: {\n    events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG,\n    restrict: (e: ICoreMouseEvent) => {\n      // no move without button\n      if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) {\n        return false;\n      }\n      return true;\n    }\n  },\n  /**\n   * ANY\n   * Events: all mouse related events\n   * Modifiers: all\n   */\n  ANY: {\n    events:\n      CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL\n      | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE,\n    restrict: (e: ICoreMouseEvent) => true\n  }\n};\n\nconst enum Modifiers {\n  SHIFT = 4,\n  ALT = 8,\n  CTRL = 16\n}\n\n// helper for default encoders to generate the event code.\nfunction eventCode(e: ICoreMouseEvent, isSGR: boolean): number {\n  let code = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0);\n  if (e.button === CoreMouseButton.WHEEL) {\n    code |= 64;\n    code |= e.action;\n  } else {\n    code |= e.button & 3;\n    if (e.button & 4) {\n      code |= 64;\n    }\n    if (e.button & 8) {\n      code |= 128;\n    }\n    if (e.action === CoreMouseAction.MOVE) {\n      code |= CoreMouseAction.MOVE;\n    } else if (e.action === CoreMouseAction.UP && !isSGR) {\n      // special case - only SGR can report button on release\n      // all others have to go with NONE\n      code |= CoreMouseButton.NONE;\n    }\n  }\n  return code;\n}\n\nconst S = String.fromCharCode;\n\n/**\n * Supported default encodings.\n */\nconst DEFAULT_ENCODINGS: { [key: string]: CoreMouseEncoding } = {\n  /**\n   * DEFAULT - CSI M Pb Px Py\n   * Single byte encoding for coords and event code.\n   * Can encode values up to 223 (1-based).\n   */\n  DEFAULT: (e: ICoreMouseEvent) => {\n    const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];\n    // supress mouse report if we exceed addressible range\n    // Note this is handled differently by emulators\n    // - xterm:         sends 0;0 coords instead\n    // - vte, konsole:  no report\n    if (params[0] > 255 || params[1] > 255 || params[2] > 255) {\n      return '';\n    }\n    return `\\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;\n  },\n  /**\n   * SGR - CSI < Pb ; Px ; Py M|m\n   * No encoding limitation.\n   * Can report button on release and works with a well formed sequence.\n   */\n  SGR: (e: ICoreMouseEvent) => {\n    const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';\n    return `\\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`;\n  },\n  SGR_PIXELS: (e: ICoreMouseEvent) => {\n    const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';\n    return `\\x1b[<${eventCode(e, true)};${e.x};${e.y}${final}`;\n  }\n};\n\n/**\n * MouseStateService\n *\n * Provides mouse tracking reports with different protocols and encodings.\n *  - protocols: NONE (default), X10, VT200, DRAG, ANY\n *  - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507)\n *\n * Custom protocols/encodings can be added by `addProtocol` / `addEncoding`.\n * To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`.\n * Switching a protocol will send a notification event `onProtocolChange`\n * with a list of needed events to track.\n *\n * The service handles the mouse tracking state and decides whether to send\n * a tracking report to the backend based on protocol and encoding limitations.\n * To send a mouse event call `triggerMouseEvent`.\n */\nexport class MouseStateService extends Disposable implements IMouseStateService {\n  public serviceBrand: any;\n\n  private _protocols: { [name: string]: ICoreMouseProtocol } = {};\n  private _encodings: { [name: string]: CoreMouseEncoding } = {};\n  private _activeProtocol: string = '';\n  private _activeEncoding: string = '';\n  private _customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined;\n\n  private readonly _onProtocolChange = this._register(new Emitter<CoreMouseEventType>());\n  public readonly onProtocolChange = this._onProtocolChange.event;\n\n  constructor() {\n    super();\n\n    // register default protocols and encodings\n    for (const name of Object.keys(DEFAULT_PROTOCOLS)) this.addProtocol(name, DEFAULT_PROTOCOLS[name]);\n    for (const name of Object.keys(DEFAULT_ENCODINGS)) this.addEncoding(name, DEFAULT_ENCODINGS[name]);\n    // call reset to set defaults\n    this.reset();\n  }\n\n  public addProtocol(name: string, protocol: ICoreMouseProtocol): void {\n    this._protocols[name] = protocol;\n  }\n\n  public addEncoding(name: string, encoding: CoreMouseEncoding): void {\n    this._encodings[name] = encoding;\n  }\n\n  public get activeProtocol(): string {\n    return this._activeProtocol;\n  }\n\n  public get areMouseEventsActive(): boolean {\n    return this._protocols[this._activeProtocol].events !== 0;\n  }\n\n  public set activeProtocol(name: string) {\n    if (!this._protocols[name]) {\n      throw new Error(`unknown protocol \"${name}\"`);\n    }\n    this._activeProtocol = name;\n    this._onProtocolChange.fire(this._protocols[name].events);\n  }\n\n  public get activeEncoding(): string {\n    return this._activeEncoding;\n  }\n\n  public set activeEncoding(name: string) {\n    if (!this._encodings[name]) {\n      throw new Error(`unknown encoding \"${name}\"`);\n    }\n    this._activeEncoding = name;\n  }\n\n  public reset(): void {\n    this.activeProtocol = 'NONE';\n    this.activeEncoding = 'DEFAULT';\n  }\n\n  public setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void {\n    this._customWheelEventHandler = customWheelEventHandler;\n  }\n\n  public allowCustomWheelEvent(ev: WheelEvent): boolean {\n    return this._customWheelEventHandler ? this._customWheelEventHandler(ev) !== false : true;\n  }\n\n  public restrictMouseEvent(e: ICoreMouseEvent): boolean {\n    return this._protocols[this._activeProtocol].restrict(e);\n  }\n\n  public encodeMouseEvent(e: ICoreMouseEvent): string {\n    return this._encodings[this._activeEncoding](e);\n  }\n\n  public get isDefaultEncoding(): boolean {\n    return this._activeEncoding === 'DEFAULT';\n  }\n\n  public get isPixelEncoding(): boolean {\n    return this._activeEncoding === 'SGR_PIXELS';\n  }\n}\n"
  },
  {
    "path": "src/common/services/OptionsService.test.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { OptionsService, DEFAULT_OPTIONS } from 'common/services/OptionsService';\nimport { IDisposable } from 'common/Types';\n\ndescribe('OptionsService', () => {\n  describe('constructor', () => {\n    const originalError = console.error;\n    beforeEach(() => {\n      console.error = () => { };\n    });\n    afterEach(() => {\n      console.error = originalError;\n    });\n    it('uses default value if invalid constructor option values passed for cols/rows', () => {\n      const optionsService = new OptionsService({ cols: undefined, rows: undefined });\n      assert.equal(optionsService.options.rows, DEFAULT_OPTIONS.rows);\n      assert.equal(optionsService.options.cols, DEFAULT_OPTIONS.cols);\n    });\n    it('uses values from constructor option values if correctly passed', () => {\n      const optionsService = new OptionsService({ cols: 80, rows: 25 });\n      assert.equal(optionsService.options.rows, 25);\n      assert.equal(optionsService.options.cols, 80);\n    });\n    it('uses default value if invalid constructor option value passed', () => {\n      assert.equal(new OptionsService({ tabStopWidth: 0 }).options.tabStopWidth, DEFAULT_OPTIONS.tabStopWidth);\n    });\n    it('object.keys return the correct number of options', () => {\n      const optionsService = new OptionsService({ cols: 80, rows: 25 });\n      assert.notEqual(Object.keys(optionsService.options).length, 0);\n    });\n  });\n  describe('setOption', () => {\n    let service: OptionsService;\n    beforeEach(() => {\n      service = new OptionsService({});\n    });\n    it('applies valid fontWeight option values', () => {\n      service.options.fontWeight = 'bold';\n      assert.equal(service.options.fontWeight, 'bold', '\"bold\" keyword value should be applied');\n\n      service.options.fontWeight = 'normal';\n      assert.equal(service.options.fontWeight, 'normal', '\"normal\" keyword value should be applied');\n\n      service.options.fontWeight = '600';\n      assert.equal(service.options.fontWeight, '600', 'String numeric values should be applied');\n\n      service.options.fontWeight = 350;\n      assert.equal(service.options.fontWeight, 350, 'Values between 1 and 1000 should be applied as is');\n\n      service.options.fontWeight = 1;\n      assert.equal(service.options.fontWeight, 1, 'Range should include minimum value: 1');\n\n      service.options.fontWeight = 1000;\n      assert.equal(service.options.fontWeight, 1000, 'Range should include maximum value: 1000');\n    });\n    it('normalizes invalid fontWeight option values', () => {\n      service.options.fontWeight = 350;\n      assert.doesNotThrow(() => service.options.fontWeight = 10000, 'fontWeight should be normalized instead of throwing');\n      assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Values greater than 1000 should be reset to default');\n\n      service.options.fontWeight = 350;\n      service.options.fontWeight = -10;\n      assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Values less than 1 should be reset to default');\n\n      service.options.fontWeight = 350;\n      service.options.fontWeight = 'bold700' as any;\n      assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Wrong string literals should be reset to default');\n    });\n  });\n  describe('onOptionChange', () => {\n    let service: OptionsService;\n    beforeEach(() => {\n      service = new OptionsService({});\n    });\n    it('should fire on any option change', async () => {\n      let disposable: IDisposable;\n      await new Promise<void>(r => {\n        disposable = service.onOptionChange(e => {\n          assert.strictEqual(e, 'cursorWidth');\n          r();\n        });\n        service.options.cursorWidth = 10;\n      });\n      disposable!.dispose();\n      await new Promise<void>(r => {\n        service.onOptionChange(e => {\n          assert.strictEqual(e, 'scrollback');\n          r();\n        });\n        service.options.scrollback = 20;\n      });\n    });\n  });\n  describe('onSpecificOptionChange', () => {\n    let service: OptionsService;\n    beforeEach(() => {\n      service = new OptionsService({});\n    });\n    it('should fire only on a specific option change', async () => {\n      await new Promise<void>(r => {\n        service.onSpecificOptionChange('scrollback', e => {\n          assert.strictEqual(e, 20);\n          r();\n        });\n        service.options.cursorWidth = 10;\n        service.options.scrollback = 20;\n      });\n    });\n  });\n  describe('onSpecificOptionChange', () => {\n    let service: OptionsService;\n    beforeEach(() => {\n      service = new OptionsService({});\n    });\n    it('should fire only on a specific option change', async () => {\n      await new Promise<void>(r => {\n        service.onSpecificOptionChange('scrollback', e => {\n          assert.strictEqual(e, 20);\n          r();\n        });\n        service.options.cursorWidth = 10;\n        service.options.scrollback = 20;\n      });\n    });\n  });\n  describe('onMultipleOptionChange', () => {\n    let service: OptionsService;\n    beforeEach(() => {\n      service = new OptionsService({});\n    });\n    it('should fire only for specific options', async () => {\n      await new Promise<void>(r => {\n        let called = false;\n        service.onMultipleOptionChange(['scrollback'], () => {\n          called = true;\n        });\n        service.options.cursorWidth = 10;\n        assert.notOk(called);\n        service.options.scrollback = 20;\n        assert.ok(called);\n        r();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/services/OptionsService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { isMac } from 'common/Platform';\nimport { CursorStyle, IDisposable } from 'common/Types';\nimport { FontWeight, IOptionsService, ITerminalOptions } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\nexport const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {\n  cols: 80,\n  rows: 24,\n  showCursorImmediately: false,\n  cursorBlink: false,\n  blinkIntervalDuration: 0,\n  cursorStyle: 'block',\n  cursorWidth: 1,\n  cursorInactiveStyle: 'outline',\n  drawBoldTextInBrightColors: true,\n  documentOverride: null,\n  fastScrollSensitivity: 5,\n  fontFamily: 'monospace',\n  fontSize: 15,\n  fontWeight: 'normal',\n  fontWeightBold: 'bold',\n  ignoreBracketedPasteMode: false,\n  lineHeight: 1.0,\n  letterSpacing: 0,\n  linkHandler: null,\n  logLevel: 'info',\n  logger: null,\n  scrollback: 1000,\n  scrollbar: { showScrollbar: true },\n  scrollOnEraseInDisplay: false,\n  scrollOnUserInput: true,\n  scrollSensitivity: 1,\n  screenReaderMode: false,\n  smoothScrollDuration: 0,\n  macOptionIsMeta: false,\n  macOptionClickForcesSelection: false,\n  minimumContrastRatio: 1,\n  disableStdin: false,\n  allowProposedApi: false,\n  allowTransparency: false,\n  tabStopWidth: 8,\n  theme: {},\n  reflowCursorLine: false,\n  rescaleOverlappingGlyphs: false,\n  rightClickSelectsWord: isMac,\n  windowOptions: {},\n  windowsPty: {},\n  wordSeparator: ' ()[]{}\\',\"`',\n  altClickMovesCursor: true,\n  convertEol: false,\n  termName: 'xterm',\n  quirks: {},\n  vtExtensions: {}\n};\n\nconst FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];\n\nexport class OptionsService extends Disposable implements IOptionsService {\n  public serviceBrand: any;\n\n  public readonly rawOptions: Required<ITerminalOptions>;\n  public options: Required<ITerminalOptions>;\n\n  private readonly _onOptionChange = this._register(new Emitter<keyof ITerminalOptions>());\n  public readonly onOptionChange = this._onOptionChange.event;\n\n  constructor(options: Partial<ITerminalOptions>) {\n    super();\n    // set the default value of each option\n    const defaultOptions = { ...DEFAULT_OPTIONS };\n    for (const key in options) {\n      if (key in defaultOptions) {\n        try {\n          const newValue = options[key];\n          defaultOptions[key] = this._sanitizeAndValidateOption(key, newValue);\n        } catch (e) {\n          console.error(e);\n        }\n      }\n    }\n\n    // set up getters and setters for each option\n    this.rawOptions = defaultOptions;\n    this.options = { ... defaultOptions };\n    this._setupOptions();\n\n    // Clear out options that could link outside xterm.js as they could easily cause an embedder\n    // memory leak\n    this._register(toDisposable(() => {\n      this.rawOptions.linkHandler = null;\n      this.rawOptions.documentOverride = null;\n    }));\n  }\n\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  public onSpecificOptionChange<T extends keyof ITerminalOptions>(key: T, listener: (value: ITerminalOptions[T]) => any): IDisposable {\n    return this.onOptionChange(eventKey => {\n      if (eventKey === key) {\n        listener(this.rawOptions[key]);\n      }\n    });\n  }\n\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable {\n    return this.onOptionChange(eventKey => {\n      if (keys.indexOf(eventKey) !== -1) {\n        listener();\n      }\n    });\n  }\n\n  private _setupOptions(): void {\n    const getter = (propName: string): any => {\n      if (!(propName in DEFAULT_OPTIONS)) {\n        throw new Error(`No option with key \"${propName}\"`);\n      }\n      return this.rawOptions[propName];\n    };\n\n    const setter = (propName: string, value: any): void => {\n      if (!(propName in DEFAULT_OPTIONS)) {\n        throw new Error(`No option with key \"${propName}\"`);\n      }\n\n      value = this._sanitizeAndValidateOption(propName, value);\n      // Don't fire an option change event if they didn't change\n      if (this.rawOptions[propName] !== value) {\n        this.rawOptions[propName] = value;\n        this._onOptionChange.fire(propName);\n      }\n    };\n\n    for (const propName in this.rawOptions) {\n      const desc = {\n        get: getter.bind(this, propName),\n        set: setter.bind(this, propName)\n      };\n      Object.defineProperty(this.options, propName, desc);\n    }\n  }\n\n  private _sanitizeAndValidateOption(key: string, value: any): any {\n    switch (key) {\n      case 'cursorStyle':\n        if (!value) {\n          value = DEFAULT_OPTIONS[key];\n        }\n        if (!isCursorStyle(value)) {\n          throw new Error(`\"${value}\" is not a valid value for ${key}`);\n        }\n        break;\n      case 'wordSeparator':\n        if (!value) {\n          value = DEFAULT_OPTIONS[key];\n        }\n        break;\n      case 'fontWeight':\n      case 'fontWeightBold':\n        if (typeof value === 'number' && 1 <= value && value <= 1000) {\n          // already valid numeric value\n          break;\n        }\n        value = FONT_WEIGHT_OPTIONS.includes(value) ? value : DEFAULT_OPTIONS[key];\n        break;\n      case 'blinkIntervalDuration':\n        value = Math.floor(value);\n        if (value < 0) {\n          throw new Error(`${key} cannot be less than 0, value: ${value}`);\n        }\n        break;\n      case 'cursorWidth':\n        value = Math.floor(value);\n        // Fall through for bounds check\n      case 'lineHeight':\n      case 'tabStopWidth':\n        if (value < 1) {\n          throw new Error(`${key} cannot be less than 1, value: ${value}`);\n        }\n        break;\n      case 'minimumContrastRatio':\n        value = Math.max(1, Math.min(21, Math.round(value * 10) / 10));\n        break;\n      case 'scrollback':\n        value = Math.min(value, 4294967295);\n        if (value < 0) {\n          throw new Error(`${key} cannot be less than 0, value: ${value}`);\n        }\n        break;\n      case 'fastScrollSensitivity':\n      case 'scrollSensitivity':\n        if (value <= 0) {\n          throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`);\n        }\n        break;\n      case 'rows':\n      case 'cols':\n        if (!value && value !== 0) {\n          throw new Error(`${key} must be numeric, value: ${value}`);\n        }\n        break;\n      case 'windowsPty':\n        value = value ?? {};\n        break;\n    }\n    return value;\n  }\n}\n\nfunction isCursorStyle(value: unknown): value is CursorStyle {\n  return value === 'block' || value === 'underline' || value === 'bar';\n}\n"
  },
  {
    "path": "src/common/services/OscLinkService.test.ts",
    "content": "/**\n * Copyright (c) 2020 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { AttributeData } from 'common/buffer/AttributeData';\nimport { BufferService } from 'common/services/BufferService';\nimport { OptionsService } from 'common/services/OptionsService';\nimport { OscLinkService } from 'common/services/OscLinkService';\nimport { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';\nimport { MockLogService } from 'common/TestUtils.test';\n\ndescribe('OscLinkService', () => {\n  describe('constructor', () => {\n    let bufferService: IBufferService;\n    let optionsService: IOptionsService;\n    let oscLinkService: IOscLinkService;\n    beforeEach(() => {\n      optionsService = new OptionsService({ rows: 3, cols: 10 });\n      bufferService = new BufferService(optionsService, new MockLogService());\n      oscLinkService = new OscLinkService(bufferService);\n    });\n\n    it('link IDs are created and fetched consistently', () => {\n      const linkId = oscLinkService.registerLink({ id: 'foo', uri: 'bar' });\n      assert.ok(linkId);\n      assert.equal(oscLinkService.registerLink({ id: 'foo', uri: 'bar' }), linkId);\n    });\n\n    it('should dispose the link ID when the last marker is trimmed from the buffer', () => {\n      // Activate the alt buffer to get 0 scrollback\n      bufferService.buffers.activateAltBuffer();\n      const linkId = oscLinkService.registerLink({ id: 'foo', uri: 'bar' });\n      assert.ok(linkId);\n      bufferService.scroll(new AttributeData());\n      assert.notStrictEqual(oscLinkService.registerLink({ id: 'foo', uri: 'bar' }), linkId);\n    });\n\n    it('should fetch link data from link id', () => {\n      const linkId = oscLinkService.registerLink({ id: 'foo', uri: 'bar' });\n      assert.deepStrictEqual(oscLinkService.getLinkData(linkId), { id: 'foo', uri: 'bar' });\n    });\n  });\n});\n"
  },
  {
    "path": "src/common/services/OscLinkService.ts",
    "content": "/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { IBufferService, IOscLinkService } from 'common/services/Services';\nimport { IMarker, IOscLinkData } from 'common/Types';\n\nexport class OscLinkService implements IOscLinkService {\n  public serviceBrand: any;\n\n  private _nextId = 1;\n\n  /**\n   * A map of the link key to link entry. This is used to add additional lines to links with ids.\n   */\n  private _entriesWithId: Map<string, IOscLinkEntryWithId> = new Map();\n\n  /**\n   * A map of the link id to the link entry. The \"link id\" (number) which is the numberic\n   * representation of a unique link should not be confused with \"id\" (string) which comes in with\n   * `id=` in the OSC link's properties.\n   */\n  private _dataByLinkId: Map<number, IOscLinkEntryNoId | IOscLinkEntryWithId> = new Map();\n\n  constructor(\n    @IBufferService private readonly _bufferService: IBufferService\n  ) {\n  }\n\n  public registerLink(data: IOscLinkData): number {\n    const buffer = this._bufferService.buffer;\n\n    // Links with no id will only ever be registered a single time\n    if (data.id === undefined) {\n      const marker = buffer.addMarker(buffer.ybase + buffer.y);\n      const entry: IOscLinkEntryNoId = {\n        data,\n        id: this._nextId++,\n        lines: [marker]\n      };\n      marker.onDispose(() => this._removeMarkerFromLink(entry, marker));\n      this._dataByLinkId.set(entry.id, entry);\n      return entry.id;\n    }\n\n    // Add the line to the link if it already exists\n    const castData = data as Required<IOscLinkData>;\n    const key = this._getEntryIdKey(castData);\n    const match = this._entriesWithId.get(key);\n    if (match) {\n      this.addLineToLink(match.id, buffer.ybase + buffer.y);\n      return match.id;\n    }\n\n    // Create the link\n    const marker = buffer.addMarker(buffer.ybase + buffer.y);\n    const entry: IOscLinkEntryWithId = {\n      id: this._nextId++,\n      key: this._getEntryIdKey(castData),\n      data: castData,\n      lines: [marker]\n    };\n    marker.onDispose(() => this._removeMarkerFromLink(entry, marker));\n    this._entriesWithId.set(entry.key, entry);\n    this._dataByLinkId.set(entry.id, entry);\n    return entry.id;\n  }\n\n  public addLineToLink(linkId: number, y: number): void {\n    const entry = this._dataByLinkId.get(linkId);\n    if (!entry) {\n      return;\n    }\n    if (entry.lines.every(e => e.line !== y)) {\n      const marker = this._bufferService.buffer.addMarker(y);\n      entry.lines.push(marker);\n      marker.onDispose(() => this._removeMarkerFromLink(entry, marker));\n    }\n  }\n\n  public getLinkData(linkId: number): IOscLinkData | undefined {\n    return this._dataByLinkId.get(linkId)?.data;\n  }\n\n  private _getEntryIdKey(linkData: Required<IOscLinkData>): string {\n    return `${linkData.id};;${linkData.uri}`;\n  }\n\n  private _removeMarkerFromLink(entry: IOscLinkEntryNoId | IOscLinkEntryWithId, marker: IMarker): void {\n    const index = entry.lines.indexOf(marker);\n    if (index === -1) {\n      return;\n    }\n    entry.lines.splice(index, 1);\n    if (entry.lines.length === 0) {\n      if (entry.data.id !== undefined) {\n        this._entriesWithId.delete((entry as IOscLinkEntryWithId).key);\n      }\n      this._dataByLinkId.delete(entry.id);\n    }\n  }\n}\n\ninterface IOscLinkEntry<T extends IOscLinkData> {\n  data: T;\n  id: number;\n  lines: IMarker[];\n}\n\ninterface IOscLinkEntryNoId extends IOscLinkEntry<IOscLinkData> {\n}\n\ninterface IOscLinkEntryWithId extends IOscLinkEntry<Required<IOscLinkData>> {\n  key: string;\n}\n"
  },
  {
    "path": "src/common/services/ServiceRegistry.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This was heavily inspired from microsoft/vscode's dependency injection system (MIT).\n */\n/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { IServiceIdentifier } from 'common/services/Services';\n\nconst DI_TARGET = 'di$target';\nconst DI_DEPENDENCIES = 'di$dependencies';\n\nexport const serviceRegistry: Map<string, IServiceIdentifier<any>> = new Map();\n\nexport function getServiceDependencies(ctor: any): { id: IServiceIdentifier<any>, index: number, optional: boolean }[] {\n  return ctor[DI_DEPENDENCIES] || [];\n}\n\nexport function createDecorator<T>(id: string): IServiceIdentifier<T> {\n  if (serviceRegistry.has(id)) {\n    return serviceRegistry.get(id)!;\n  }\n\n  const decorator: any = function (target: Function, key: string, index: number): any {\n    if (arguments.length !== 3) {\n      throw new Error('@IServiceName-decorator can only be used to decorate a parameter');\n    }\n\n    storeServiceDependency(decorator, target, index);\n  };\n\n  decorator._id = id;\n\n  serviceRegistry.set(id, decorator);\n  return decorator;\n}\n\nfunction storeServiceDependency(id: Function, target: Function, index: number): void {\n  if ((target as any)[DI_TARGET] === target) {\n    (target as any)[DI_DEPENDENCIES].push({ id, index });\n  } else {\n    (target as any)[DI_DEPENDENCIES] = [{ id, index }];\n    (target as any)[DI_TARGET] = target;\n  }\n}\n"
  },
  {
    "path": "src/common/services/Services.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { IDecoration, IDecorationOptions, ILinkHandler, ILogger, IWindowsPty, IOverviewRulerOptions } from '@xterm/xterm';\nimport { CoreMouseEncoding, CoreMouseEventType, CursorInactiveStyle, CursorStyle, IAttributeData, ICharset, IColor, ICoreMouseEvent, ICoreMouseProtocol, IDecPrivateModes, IDisposable, IKittyKeyboardState, IModes, IOscLinkData, IWindowOptions } from 'common/Types';\nimport { IBuffer, IBufferSet } from 'common/buffer/Types';\nimport { createDecorator } from 'common/services/ServiceRegistry';\nimport type { Emitter, IEvent } from 'common/Event';\n\nexport const IBufferService = createDecorator<IBufferService>('BufferService');\nexport interface IBufferService {\n  serviceBrand: undefined;\n\n  readonly cols: number;\n  readonly rows: number;\n  readonly buffer: IBuffer;\n  readonly buffers: IBufferSet;\n  isUserScrolling: boolean;\n  onResize: IEvent<IBufferResizeEvent>;\n  onScroll: IEvent<number>;\n  scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void;\n  scrollLines(disp: number, suppressScrollEvent?: boolean): void;\n  resize(cols: number, rows: number): void;\n  reset(): void;\n}\n\nexport interface IBufferResizeEvent {\n  cols: number;\n  rows: number;\n  colsChanged: boolean;\n  rowsChanged: boolean;\n}\n\nexport const IMouseStateService = createDecorator<IMouseStateService>('MouseStateService');\nexport interface IMouseStateService {\n  serviceBrand: undefined;\n\n  activeProtocol: string;\n  activeEncoding: string;\n  areMouseEventsActive: boolean;\n  addProtocol(name: string, protocol: ICoreMouseProtocol): void;\n  addEncoding(name: string, encoding: CoreMouseEncoding): void;\n  reset(): void;\n  setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void;\n  allowCustomWheelEvent(ev: WheelEvent): boolean;\n\n  /**\n   * Event to announce changes in mouse tracking.\n   */\n  onProtocolChange: IEvent<CoreMouseEventType>;\n  restrictMouseEvent(event: ICoreMouseEvent): boolean;\n  encodeMouseEvent(event: ICoreMouseEvent): string;\n  readonly isDefaultEncoding: boolean;\n  readonly isPixelEncoding: boolean;\n}\n\nexport const ICoreService = createDecorator<ICoreService>('CoreService');\nexport interface ICoreService {\n  serviceBrand: undefined;\n\n  /**\n   * Initially the cursor will not be visible until the first time the terminal\n   * is focused.\n   */\n  isCursorInitialized: boolean;\n  isCursorHidden: boolean;\n\n  readonly modes: IModes;\n  readonly decPrivateModes: IDecPrivateModes;\n  readonly kittyKeyboard: IKittyKeyboardState;\n\n  readonly onData: IEvent<string>;\n  readonly onUserInput: IEvent<void>;\n  readonly onBinary: IEvent<string>;\n  readonly onRequestScrollToBottom: IEvent<void>;\n\n  reset(): void;\n\n  /**\n   * Triggers the onData event in the public API.\n   * @param data The data that is being emitted.\n   * @param wasUserInput Whether the data originated from the user (as opposed to\n   * resulting from parsing incoming data). When true this will also:\n   * - Scroll to the bottom of the buffer if option scrollOnUserInput is true.\n   * - Fire the `onUserInput` event (so selection can be cleared).\n   */\n  triggerDataEvent(data: string, wasUserInput?: boolean): void;\n\n  /**\n   * Triggers the onBinary event in the public API.\n   * @param data The data that is being emitted.\n   */\n  triggerBinaryEvent(data: string): void;\n}\n\nexport const ICharsetService = createDecorator<ICharsetService>('CharsetService');\nexport interface ICharsetService {\n  serviceBrand: undefined;\n\n  charset: ICharset | undefined;\n  readonly glevel: number;\n  readonly charsets: (ICharset | undefined)[];\n\n  reset(): void;\n\n  /**\n   * Set the G level of the terminal.\n   * @param g\n   */\n  setgLevel(g: number): void;\n\n  /**\n   * Set the charset for the given G level of the terminal.\n   * @param g\n   * @param charset\n   */\n  setgCharset(g: number, charset: ICharset | undefined): void;\n}\n\nexport interface IServiceIdentifier<T> {\n  (...args: any[]): void;\n  type: T;\n  _id: string;\n}\n\nexport interface IBrandedService {\n  serviceBrand: undefined;\n}\n\ntype GetLeadingNonServiceArgs<TArgs extends any[]> = TArgs extends [] ? []\n  : TArgs extends [...infer TFirst, infer TLast] ? TLast extends IBrandedService ? GetLeadingNonServiceArgs<TFirst> : TArgs\n    : never;\n\nexport const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService');\nexport interface IInstantiationService {\n  serviceBrand: undefined;\n\n  setService<T>(id: IServiceIdentifier<T>, instance: T): void;\n  getService<T>(id: IServiceIdentifier<T>): T | undefined;\n  createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;\n}\n\nexport enum LogLevelEnum {\n  TRACE = 0,\n  DEBUG = 1,\n  INFO = 2,\n  WARN = 3,\n  ERROR = 4,\n  OFF = 5\n}\n\nexport const ILogService = createDecorator<ILogService>('LogService');\nexport interface ILogService {\n  serviceBrand: undefined;\n\n  readonly logLevel: LogLevelEnum;\n\n  trace(message: any, ...optionalParams: any[]): void;\n  debug(message: any, ...optionalParams: any[]): void;\n  info(message: any, ...optionalParams: any[]): void;\n  warn(message: any, ...optionalParams: any[]): void;\n  error(message: any, ...optionalParams: any[]): void;\n}\n\nexport const IOptionsService = createDecorator<IOptionsService>('OptionsService');\nexport interface IOptionsService {\n  serviceBrand: undefined;\n\n  /**\n   * Read only access to the raw options object, this is an internal-only fast path for accessing\n   * single options without any validation as we trust TypeScript to enforce correct usage\n   * internally.\n   */\n  readonly rawOptions: Required<ITerminalOptions>;\n\n  /**\n   * Options as exposed through the public API, this property uses getters and setters with\n   * validation which makes it safer but slower. {@link rawOptions} should be used for pretty much\n   * all internal usage for performance reasons.\n   */\n  readonly options: Required<ITerminalOptions>;\n\n  /**\n   * Adds an event listener for when any option changes.\n   */\n  readonly onOptionChange: IEvent<keyof ITerminalOptions>;\n\n  /**\n   * Adds an event listener for when a specific option changes, this is a convenience method that is\n   * preferred over {@link onOptionChange} when only a single option is being listened to.\n   */\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  onSpecificOptionChange<T extends keyof ITerminalOptions>(key: T, listener: (arg1: Required<ITerminalOptions>[T]) => any): IDisposable;\n\n  /**\n   * Adds an event listener for when a set of specific options change, this is a convenience method\n   * that is preferred over {@link onOptionChange} when multiple options are being listened to and\n   * handled the same way.\n   */\n  // eslint-disable-next-line @typescript-eslint/naming-convention\n  onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable;\n}\n\nexport type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;\nexport type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';\n\nexport interface ITerminalOptions {\n  allowProposedApi?: boolean;\n  allowTransparency?: boolean;\n  altClickMovesCursor?: boolean;\n  cols?: number;\n  convertEol?: boolean;\n  cursorBlink?: boolean;\n  blinkIntervalDuration?: number;\n  cursorStyle?: CursorStyle;\n  cursorWidth?: number;\n  cursorInactiveStyle?: CursorInactiveStyle;\n  disableStdin?: boolean;\n  documentOverride?: any | null;\n  drawBoldTextInBrightColors?: boolean;\n  fastScrollSensitivity?: number;\n  fontSize?: number;\n  fontFamily?: string;\n  fontWeight?: FontWeight;\n  fontWeightBold?: FontWeight;\n  ignoreBracketedPasteMode?: boolean;\n  letterSpacing?: number;\n  lineHeight?: number;\n  linkHandler?: ILinkHandler | null;\n  logLevel?: LogLevel;\n  logger?: ILogger | null;\n  macOptionIsMeta?: boolean;\n  macOptionClickForcesSelection?: boolean;\n  minimumContrastRatio?: number;\n  reflowCursorLine?: boolean;\n  rescaleOverlappingGlyphs?: boolean;\n  rightClickSelectsWord?: boolean;\n  rows?: number;\n  showCursorImmediately?: boolean;\n  screenReaderMode?: boolean;\n  scrollback?: number;\n  scrollOnUserInput?: boolean;\n  scrollSensitivity?: number;\n  smoothScrollDuration?: number;\n  tabStopWidth?: number;\n  theme?: ITheme;\n  windowsPty?: IWindowsPty;\n  windowOptions?: IWindowOptions;\n  wordSeparator?: string;\n  quirks?: ITerminalQuirks;\n  scrollbar?: IScrollbarOptions;\n  scrollOnEraseInDisplay?: boolean;\n  vtExtensions?: IVtExtensions;\n\n  [key: string]: any;\n  termName: string;\n}\n\nexport interface ITheme {\n  foreground?: string;\n  background?: string;\n  cursor?: string;\n  cursorAccent?: string;\n  selectionForeground?: string;\n  selectionBackground?: string;\n  selectionInactiveBackground?: string;\n  scrollbarSliderBackground?: string;\n  scrollbarSliderHoverBackground?: string;\n  scrollbarSliderActiveBackground?: string;\n  overviewRulerBorder?: string;\n  black?: string;\n  red?: string;\n  green?: string;\n  yellow?: string;\n  blue?: string;\n  magenta?: string;\n  cyan?: string;\n  white?: string;\n  brightBlack?: string;\n  brightRed?: string;\n  brightGreen?: string;\n  brightYellow?: string;\n  brightBlue?: string;\n  brightMagenta?: string;\n  brightCyan?: string;\n  brightWhite?: string;\n  extendedAnsi?: string[];\n}\n\nexport interface ITerminalQuirks {\n  allowSetCursorBlink?: boolean;\n}\n\nexport interface IScrollbarOptions {\n  showScrollbar?: boolean;\n  showArrows?: boolean;\n  width?: number;\n  overviewRuler?: IOverviewRulerOptions;\n}\n\nexport interface IVtExtensions {\n  kittyKeyboard?: boolean;\n  kittySgrBoldFaintControl?: boolean;\n  win32InputMode?: boolean;\n  colorSchemeQuery?: boolean;\n}\n\nexport const IOscLinkService = createDecorator<IOscLinkService>('OscLinkService');\nexport interface IOscLinkService {\n  serviceBrand: undefined;\n  /**\n   * Registers a link to the service, returning the link ID. The link data is managed by this\n   * service and will be freed when this current cursor position is trimmed off the buffer.\n   */\n  registerLink(linkData: IOscLinkData): number;\n  /**\n   * Adds a line to a link if needed.\n   */\n  addLineToLink(linkId: number, y: number): void;\n  /** Get the link data associated with a link ID. */\n  getLinkData(linkId: number): IOscLinkData | undefined;\n}\n\n/*\n * Width and Grapheme_Cluster_Break properties of a character as a bit mask.\n *\n * bit 0: shouldJoin - should combine with preceding character.\n * bit 1..2: wcwidth - see UnicodeCharWidth.\n * bit 3..31: class of character (currently only 4 bits are used).\n *   This is used to determined grapheme clustering - i.e. which codepoints\n *   are to be combined into a single compound character.\n *\n * Use the UnicodeService static function createPropertyValue to create a\n * UnicodeCharProperties; use extractShouldJoin, extractWidth, and\n * extractCharKind to extract the components.\n */\nexport type UnicodeCharProperties = number;\n\n/**\n * Width in columns of a character.\n * In a CJK context, \"half-width\" characters (such as Latin) are width 1,\n * while \"full-width\" characters (such as Kanji) are 2 columns wide.\n * Combining characters (such as accents) are width 0.\n */\nexport type UnicodeCharWidth = 0 | 1 | 2;\n\nexport const IUnicodeService = createDecorator<IUnicodeService>('UnicodeService');\nexport interface IUnicodeService {\n  serviceBrand: undefined;\n  /** Register an Unicode version provider. */\n  register(provider: IUnicodeVersionProvider): void;\n  /** Registered Unicode versions. */\n  readonly versions: string[];\n  /** Currently active version. */\n  activeVersion: string;\n  /** Event triggered, when activate version changed. */\n  readonly onChange: IEvent<string>;\n\n  /**\n   * Unicode version dependent\n   */\n  wcwidth(codepoint: number): UnicodeCharWidth;\n  getStringCellWidth(s: string): number;\n  /**\n   * Return character width and type for grapheme clustering.\n   * If preceding != 0, it is the return code from the previous character;\n   * in that case the result specifies if the characters should be joined.\n   */\n  charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties;\n}\n\nexport interface IUnicodeVersionProvider {\n  readonly version: string;\n  wcwidth(ucs: number): UnicodeCharWidth;\n  charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties;\n}\n\nexport const IDecorationService = createDecorator<IDecorationService>('DecorationService');\nexport interface IDecorationService extends IDisposable {\n  serviceBrand: undefined;\n  readonly decorations: IterableIterator<IInternalDecoration>;\n  readonly onDecorationRegistered: IEvent<IInternalDecoration>;\n  readonly onDecorationRemoved: IEvent<IInternalDecoration>;\n  registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;\n  reset(): void;\n  /**\n   * Trigger a callback over the decoration at a cell (in no particular order). This uses a callback\n   * instead of an iterator as it's typically used in hot code paths.\n   */\n  forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void;\n}\nexport interface IInternalDecoration extends IDecoration {\n  readonly options: IDecorationOptions;\n  readonly backgroundColorRGB: IColor | undefined;\n  readonly foregroundColorRGB: IColor | undefined;\n  readonly onRenderEmitter: Emitter<HTMLElement>;\n}\n"
  },
  {
    "path": "src/common/services/UnicodeService.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { assert } from 'chai';\nimport { UnicodeService } from 'common/services/UnicodeService';\nimport { IUnicodeVersionProvider } from 'common/services/Services';\n\nclass DummyProvider implements IUnicodeVersionProvider {\n  public version = '123';\n  public wcwidth(n: number): 0 | 1 | 2 {\n    return 2;\n  }\n  public charProperties(codepoint: number): number {\n    return UnicodeService.createPropertyValue(0, this.wcwidth(codepoint));\n  }\n}\n\ndescribe('unicode provider', () => {\n  let us: UnicodeService;\n  beforeEach(() => {\n    us = new UnicodeService();\n  });\n  it('default to V6', () => {\n    assert.equal(us.activeVersion, '6');\n    assert.deepEqual(us.versions, ['6']);\n    assert.doesNotThrow(() => { us.activeVersion = '6'; }, `unknown Unicode version \"6\"`);\n    assert.equal(us.getStringCellWidth('hello'), 5);\n  });\n  it('activate should throw for unknown version', () => {\n    assert.throws(() => { us.activeVersion = '55'; }, 'unknown Unicode version \"55\"');\n  });\n  it('should notify about version change', () => {\n    const notes: string[] = [];\n    us.onChange(version => notes.push(version));\n    const dummyProvider = new DummyProvider();\n    us.register(dummyProvider);\n    us.activeVersion = dummyProvider.version;\n    assert.deepEqual(notes, [dummyProvider.version]);\n  });\n  it('correctly changes provider impl', () => {\n    assert.equal(us.getStringCellWidth('hello'), 5);\n    const dummyProvider = new DummyProvider();\n    us.register(dummyProvider);\n    us.activeVersion = dummyProvider.version;\n    assert.equal(us.getStringCellWidth('hello'), 2 * 5);\n  });\n  it('wcwidth V6 emoji test', () => {\n    const widthV6 = us.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣');\n    assert.equal(widthV6, 10);\n  });\n});\n"
  },
  {
    "path": "src/common/services/UnicodeService.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { UnicodeV6 } from 'common/input/UnicodeV6';\nimport { IUnicodeService, IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';\nimport { Emitter } from 'common/Event';\n\nexport class UnicodeService implements IUnicodeService {\n  public serviceBrand: any;\n\n  private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null);\n  private _active: string = '';\n  private _activeProvider: IUnicodeVersionProvider;\n\n  private readonly _onChange = new Emitter<string>();\n  public readonly onChange = this._onChange.event;\n\n  public static extractShouldJoin(value: UnicodeCharProperties): boolean {\n    return (value & 1) !== 0;\n  }\n  public static extractWidth(value: UnicodeCharProperties): UnicodeCharWidth {\n    return ((value >> 1) & 0x3) as UnicodeCharWidth;\n  }\n  public static extractCharKind(value: UnicodeCharProperties): number {\n    return value >> 3;\n  }\n  public static createPropertyValue(state: number, width: number, shouldJoin: boolean = false): UnicodeCharProperties {\n    return ((state & 0xffffff) << 3) | ((width & 3) << 1) | (shouldJoin?1:0);\n  }\n\n  constructor() {\n    const defaultProvider = new UnicodeV6();\n    this.register(defaultProvider);\n    this._active = defaultProvider.version;\n    this._activeProvider = defaultProvider;\n  }\n\n  public dispose(): void {\n    this._onChange.dispose();\n  }\n\n  public get versions(): string[] {\n    return Object.keys(this._providers);\n  }\n\n  public get activeVersion(): string {\n    return this._active;\n  }\n\n  public set activeVersion(version: string) {\n    if (!this._providers[version]) {\n      throw new Error(`unknown Unicode version \"${version}\"`);\n    }\n    this._active = version;\n    this._activeProvider = this._providers[version];\n    this._onChange.fire(version);\n  }\n\n  public register(provider: IUnicodeVersionProvider): void {\n    this._providers[provider.version] = provider;\n  }\n\n  /**\n   * Unicode version dependent interface.\n   */\n  public wcwidth(num: number): UnicodeCharWidth {\n    return this._activeProvider.wcwidth(num);\n  }\n\n  public getStringCellWidth(s: string): number {\n    let result = 0;\n    let precedingInfo = 0;\n    const length = s.length;\n    for (let i = 0; i < length; ++i) {\n      let code = s.charCodeAt(i);\n      // surrogate pair first\n      if (0xD800 <= code && code <= 0xDBFF) {\n        if (++i >= length) {\n          // this should not happen with strings retrieved from\n          // Buffer.translateToString as it converts from UTF-32\n          // and therefore always should contain the second part\n          // for any other string we still have to handle it somehow:\n          // simply treat the lonely surrogate first as a single char (UCS-2 behavior)\n          return result + this.wcwidth(code);\n        }\n        const second = s.charCodeAt(i);\n        // convert surrogate pair to high codepoint only for valid second part (UTF-16)\n        // otherwise treat them independently (UCS-2 behavior)\n        if (0xDC00 <= second && second <= 0xDFFF) {\n          code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;\n        } else {\n          result += this.wcwidth(second);\n        }\n      }\n      const currentInfo = this.charProperties(code, precedingInfo);\n      let chWidth = UnicodeService.extractWidth(currentInfo);\n      if (UnicodeService.extractShouldJoin(currentInfo)) {\n        chWidth -= UnicodeService.extractWidth(precedingInfo);\n      }\n      result += chWidth;\n      precedingInfo = currentInfo;\n    }\n    return result;\n  }\n\n  public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {\n    return this._activeProvider.charProperties(codepoint, preceding);\n  }\n}\n"
  },
  {
    "path": "src/common/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig-library-base\",\n  \"compilerOptions\": {\n    \"lib\": [\n      \"es2015\",\n      \"es2016.Array.Include\"\n    ],\n    \"outDir\": \"../../out\",\n    \"types\": [\n      \"../../node_modules/@types/mocha\"\n    ],\n    \"paths\": {\n      \"common/*\": [ \"./../common/*\" ],\n      \"*\": [ \"./../*\" ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../typings/xterm.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "src/headless/Terminal.ts",
    "content": "/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n *   Fabrice Bellard's javascript vt100 for jslinux:\n *   http://bellard.org/jslinux/\n *   Copyright (c) 2011 Fabrice Bellard\n *   The original design remains. The terminal itself\n *   has been extended to include xterm CSI codes, among\n *   other features.\n *\n * Terminal Emulation References:\n *   http://vt100.net/\n *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n *   http://invisible-island.net/vttest/\n *   http://www.inwap.com/pdp10/ansicode.txt\n *   http://linux.die.net/man/4/console_codes\n *   http://linux.die.net/man/7/urxvt\n */\n\nimport { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { IBuffer } from 'common/buffer/Types';\nimport { CoreTerminal } from 'common/CoreTerminal';\nimport { IMarker, ITerminalOptions } from 'common/Types';\nimport { Emitter, EventUtils } from 'common/Event';\n\nexport class Terminal extends CoreTerminal {\n  private readonly _onBell = this._register(new Emitter<void>());\n  public readonly onBell = this._onBell.event;\n  private readonly _onCursorMove = this._register(new Emitter<void>());\n  public readonly onCursorMove = this._onCursorMove.event;\n  private readonly _onTitleChange = this._register(new Emitter<string>());\n  public readonly onTitleChange = this._onTitleChange.event;\n  private readonly _onA11yCharEmitter = this._register(new Emitter<string>());\n  public readonly onA11yChar = this._onA11yCharEmitter.event;\n  private readonly _onA11yTabEmitter = this._register(new Emitter<number>());\n  public readonly onA11yTab = this._onA11yTabEmitter.event;\n\n  constructor(\n    options: ITerminalOptions = {}\n  ) {\n    super(options);\n\n    this._setup();\n\n    // Setup InputHandler listeners\n    this._register(this._inputHandler.onRequestBell(() => this.bell()));\n    this._register(this._inputHandler.onRequestReset(() => this.reset()));\n    this._register(EventUtils.forward(this._inputHandler.onCursorMove, this._onCursorMove));\n    this._register(EventUtils.forward(this._inputHandler.onTitleChange, this._onTitleChange));\n    this._register(EventUtils.forward(this._inputHandler.onA11yChar, this._onA11yCharEmitter));\n    this._register(EventUtils.forward(this._inputHandler.onA11yTab, this._onA11yTabEmitter));\n    this._register(EventUtils.forward(EventUtils.map(this._inputHandler.onRequestRefreshRows, e => ({ start: e?.start ?? 0, end: e?.end ?? this.rows - 1 })), this._onRender));\n  }\n\n  /**\n   * Convenience property to active buffer.\n   */\n  public get buffer(): IBuffer {\n    return this.buffers.active;\n  }\n\n  // TODO: Support paste here?\n\n  public get markers(): IMarker[] {\n    return this.buffer.markers;\n  }\n\n  public addMarker(cursorYOffset: number): IMarker | undefined {\n    // Disallow markers on the alt buffer\n    if (this.buffer !== this.buffers.normal) {\n      return;\n    }\n\n    return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);\n  }\n\n  public bell(): void {\n    this._onBell.fire();\n  }\n\n  public input(data: string, wasUserInput: boolean = true): void {\n    this.coreService.triggerDataEvent(data, wasUserInput);\n  }\n\n  /**\n   * Resizes the terminal.\n   *\n   * @param x The number of columns to resize to.\n   * @param y The number of rows to resize to.\n   */\n  public resize(x: number, y: number): void {\n    if (x === this.cols && y === this.rows) {\n      return;\n    }\n\n    super.resize(x, y);\n  }\n\n  /**\n   * Clear the entire buffer, making the prompt line the new first line.\n   */\n  public clear(): void {\n    if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n      // Don't clear if it's already clear\n      return;\n    }\n    this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);\n    this.buffer.lines.length = 1;\n    this.buffer.ydisp = 0;\n    this.buffer.ybase = 0;\n    this.buffer.y = 0;\n    for (let i = 1; i < this.rows; i++) {\n      this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n    }\n    this._onScroll.fire({ position: this.buffer.ydisp });\n  }\n\n  /**\n   * Reset terminal.\n   * Note: Calling this directly from JS is synchronous but does not clear\n   * input buffers and does not reset the parser, thus the terminal will\n   * continue to apply pending input data.\n   * If you need in band reset (synchronous with input data) consider\n   * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).\n   */\n  public reset(): void {\n    /**\n     * Since _setup handles a full terminal creation, we have to carry forward\n     * a few things that should not reset.\n     */\n    this.options.rows = this.rows;\n    this.options.cols = this.cols;\n\n    this._setup();\n    super.reset();\n  }\n}\n"
  },
  {
    "path": "src/headless/public/Terminal.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { deepStrictEqual, strictEqual, throws } from 'assert';\nimport { Terminal } from 'headless/public/Terminal';\nimport { ITerminalOptions } from '@xterm/headless';\n\nlet term: Terminal;\n\ndescribe('Headless API Tests', function (): void {\n  beforeEach(() => {\n    // Create default terminal to be used by most tests\n    term = new Terminal({ allowProposedApi: true });\n  });\n\n  it('Default options', async () => {\n    strictEqual(term.cols, 80);\n    strictEqual(term.rows, 24);\n  });\n\n  it('Proposed API check', async () => {\n    term = new Terminal({ allowProposedApi: false });\n    throws(() => term.unicode, (error: any) => error.message === 'You must set the allowProposedApi option to true to use proposed API');\n  });\n\n  it('write', async () => {\n    await writeSync('foo');\n    await writeSync('bar');\n    await writeSync('文');\n    lineEquals(0, 'foobar文');\n  });\n\n  it('write with callback', async () => {\n    let result: string | undefined;\n    await new Promise<void>(r => {\n      term.write('foo', () => { result = 'a'; });\n      term.write('bar', () => { result += 'b'; });\n      term.write('文', () => {\n        result += 'c';\n        r();\n      });\n    });\n    lineEquals(0, 'foobar文');\n    strictEqual(result, 'abc');\n  });\n\n  it('write - bytes (UTF8)', async () => {\n    await writeSync(new Uint8Array([102, 111, 111])); // foo\n    await writeSync(new Uint8Array([98, 97, 114])); // bar\n    await writeSync(new Uint8Array([230, 150, 135])); // 文\n    lineEquals(0, 'foobar文');\n  });\n\n  it('write - bytes (UTF8) with callback', async () => {\n    let result: string | undefined;\n    await new Promise<void>(r => {\n      term.write(new Uint8Array([102, 111, 111]), () => { result = 'A'; }); // foo\n      term.write(new Uint8Array([98, 97, 114]), () => { result += 'B'; }); // bar\n      term.write(new Uint8Array([230, 150, 135]), () => { // 文\n        result += 'C';\n        r();\n      });\n    });\n    lineEquals(0, 'foobar文');\n    strictEqual(result, 'ABC');\n  });\n\n  it('writeln', async () => {\n    await writelnSync('foo');\n    await writelnSync('bar');\n    await writelnSync('文');\n    lineEquals(0, 'foo');\n    lineEquals(1, 'bar');\n    lineEquals(2, '文');\n  });\n\n  it('writeln with callback', async () => {\n    let result: string | undefined;\n    await new Promise<void>(r => {\n      term.writeln('foo', () => { result = '1'; });\n      term.writeln('bar', () => { result += '2'; });\n      term.writeln('文', () => {\n        result += '3';\n        r();\n      });\n    });\n    lineEquals(0, 'foo');\n    lineEquals(1, 'bar');\n    lineEquals(2, '文');\n    strictEqual(result, '123');\n  });\n\n  it('writeln - bytes (UTF8)', async () => {\n    await writelnSync(new Uint8Array([102, 111, 111]));\n    await writelnSync(new Uint8Array([98, 97, 114]));\n    await writelnSync(new Uint8Array([230, 150, 135]));\n    lineEquals(0, 'foo');\n    lineEquals(1, 'bar');\n    lineEquals(2, '文');\n  });\n\n  it('clear', async () => {\n    term = new Terminal({ rows: 5, allowProposedApi: true });\n    for (let i = 0; i < 10; i++) {\n      await writeSync('\\n\\rtest' + i);\n    }\n    term.clear();\n    strictEqual(term.buffer.active.length, 5);\n    lineEquals(0, 'test9');\n    for (let i = 1; i < 5; i++) {\n      lineEquals(i, '');\n    }\n  });\n\n  describe('options', () => {\n    const termOptions = {\n      cols: 80,\n      rows: 24\n    };\n\n    beforeEach(async () => {\n      term = new Terminal(termOptions);\n    });\n    it('get options', () => {\n      const options: ITerminalOptions = term.options;\n      strictEqual(options.lineHeight, 1);\n      strictEqual(options.cursorWidth, 1);\n    });\n    it('set options', async () => {\n      term.options.scrollback = 1;\n      strictEqual(term.options.scrollback, 1);\n      term.options= {\n        fontSize: 12,\n        fontFamily: 'Arial'\n      };\n      strictEqual(term.options.fontSize, 12);\n      strictEqual(term.options.fontFamily, 'Arial');\n    });\n  });\n\n\n  describe('loadAddon', () => {\n    it('constructor', async () => {\n      term = new Terminal({ cols: 5 });\n      let cols = 0;\n      term.loadAddon({\n        activate: (t) => cols = t.cols,\n        dispose: () => { }\n      });\n      strictEqual(cols, 5);\n    });\n\n    it('dispose (addon)', async () => {\n      let disposeCalled = false;\n      const addon = {\n        activate: () => { },\n        dispose: () => disposeCalled = true\n      };\n      term.loadAddon(addon);\n      strictEqual(disposeCalled, false);\n      addon.dispose();\n      strictEqual(disposeCalled, true);\n    });\n\n    it('dispose (terminal)', async () => {\n      let disposeCalled = false;\n      term.loadAddon({\n        activate: () => { },\n        dispose: () => disposeCalled = true\n      });\n      strictEqual(disposeCalled, false);\n      term.dispose();\n      strictEqual(disposeCalled, true);\n    });\n  });\n\n  describe('Events', () => {\n    it('onCursorMove', async () => {\n      let callCount = 0;\n      term.onCursorMove(e => callCount++);\n      await writeSync('foo');\n      strictEqual(callCount, 1);\n      await writeSync('bar');\n      strictEqual(callCount, 2);\n    });\n\n    it('onData', async () => {\n      const calls: string[] = [];\n      term.onData(e => calls.push(e));\n      await writeSync('\\x1b[5n'); // DSR Status Report\n      deepStrictEqual(calls, ['\\x1b[0n']);\n    });\n\n    it('onLineFeed', async () => {\n      let callCount = 0;\n      term.onLineFeed(() => callCount++);\n      await writelnSync('foo');\n      strictEqual(callCount, 1);\n      await writelnSync('bar');\n      strictEqual(callCount, 2);\n    });\n\n    it('onRender', async () => {\n      const calls: { start: number, end: number }[] = [];\n      term.onRender(e => calls.push(e));\n      await writeSync('foo');\n      deepStrictEqual(calls, [{ start: 0, end: 0 }]);\n      await writeSync('\\n\\nbar');\n      deepStrictEqual(calls, [{ start: 0, end: 0 }, { start: 0, end: 2 }]);\n    });\n\n    it('onScroll', async () => {\n      term = new Terminal({ rows: 5 });\n      const calls: number[] = [];\n      term.onScroll(e => calls.push(e));\n      for (let i = 0; i < 4; i++) {\n        await writelnSync('foo');\n      }\n      deepStrictEqual(calls, []);\n      await writelnSync('bar');\n      deepStrictEqual(calls, [1]);\n      await writelnSync('baz');\n      deepStrictEqual(calls, [1, 2]);\n    });\n\n    it('onResize', async () => {\n      const calls: [number, number][] = [];\n      term.onResize(e => calls.push([e.cols, e.rows]));\n      deepStrictEqual(calls, []);\n      term.resize(10, 5);\n      deepStrictEqual(calls, [[10, 5]]);\n      term.resize(20, 15);\n      deepStrictEqual(calls, [[10, 5], [20, 15]]);\n    });\n\n    it('onTitleChange', async () => {\n      const calls: string[] = [];\n      term.onTitleChange(e => calls.push(e));\n      deepStrictEqual(calls, []);\n      await writeSync('\\x1b]2;foo\\x9c');\n      deepStrictEqual(calls, ['foo']);\n    });\n\n    it('onBell', async () => {\n      const calls: boolean[] = [];\n      term.onBell(() => calls.push(true));\n      deepStrictEqual(calls, []);\n      await writeSync('\\x07');\n      deepStrictEqual(calls, [true]);\n    });\n  });\n\n  describe('buffer', () => {\n    it('cursorX, cursorY', async () => {\n      term = new Terminal({ rows: 5, cols: 5, allowProposedApi: true });\n      strictEqual(term.buffer.active.cursorX, 0);\n      strictEqual(term.buffer.active.cursorY, 0);\n      await writeSync('foo');\n      strictEqual(term.buffer.active.cursorX, 3);\n      strictEqual(term.buffer.active.cursorY, 0);\n      await writeSync('\\n');\n      strictEqual(term.buffer.active.cursorX, 3);\n      strictEqual(term.buffer.active.cursorY, 1);\n      await writeSync('\\r');\n      strictEqual(term.buffer.active.cursorX, 0);\n      strictEqual(term.buffer.active.cursorY, 1);\n      await writeSync('abcde');\n      strictEqual(term.buffer.active.cursorX, 5);\n      strictEqual(term.buffer.active.cursorY, 1);\n      await writeSync('\\n\\r\\n\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.cursorX, 0);\n      strictEqual(term.buffer.active.cursorY, 4);\n    });\n\n    it('viewportY', async () => {\n      term = new Terminal({ rows: 5, allowProposedApi: true });\n      strictEqual(term.buffer.active.viewportY, 0);\n      await writeSync('\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.viewportY, 0);\n      await writeSync('\\n');\n      strictEqual(term.buffer.active.viewportY, 1);\n      await writeSync('\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.viewportY, 5);\n      term.scrollLines(-1);\n      strictEqual(term.buffer.active.viewportY, 4);\n      term.scrollToTop();\n      strictEqual(term.buffer.active.viewportY, 0);\n    });\n\n    it('baseY', async () => {\n      term = new Terminal({ rows: 5, allowProposedApi: true });\n      strictEqual(term.buffer.active.baseY, 0);\n      await writeSync('\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.baseY, 0);\n      await writeSync('\\n');\n      strictEqual(term.buffer.active.baseY, 1);\n      await writeSync('\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.baseY, 5);\n      term.scrollLines(-1);\n      strictEqual(term.buffer.active.baseY, 5);\n      term.scrollToTop();\n      strictEqual(term.buffer.active.baseY, 5);\n    });\n\n    it('length', async () => {\n      term = new Terminal({ rows: 5, allowProposedApi: true });\n      strictEqual(term.buffer.active.length, 5);\n      await writeSync('\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.length, 5);\n      await writeSync('\\n');\n      strictEqual(term.buffer.active.length, 6);\n      await writeSync('\\n\\n\\n\\n');\n      strictEqual(term.buffer.active.length, 10);\n    });\n\n    describe('getLine', () => {\n      it('invalid index', async () => {\n        term = new Terminal({ rows: 5, allowProposedApi: true });\n        strictEqual(term.buffer.active.getLine(-1), undefined);\n        strictEqual(term.buffer.active.getLine(5), undefined);\n      });\n\n      it('isWrapped', async () => {\n        term = new Terminal({ cols: 5, allowProposedApi: true });\n        strictEqual(term.buffer.active.getLine(0)!.isWrapped, false);\n        strictEqual(term.buffer.active.getLine(1)!.isWrapped, false);\n        await writeSync('abcde');\n        strictEqual(term.buffer.active.getLine(0)!.isWrapped, false);\n        strictEqual(term.buffer.active.getLine(1)!.isWrapped, false);\n        await writeSync('f');\n        strictEqual(term.buffer.active.getLine(0)!.isWrapped, false);\n        strictEqual(term.buffer.active.getLine(1)!.isWrapped, true);\n      });\n\n      it('translateToString', async () => {\n        term = new Terminal({ cols: 5, allowProposedApi: true });\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(), '     ');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(true), '');\n        await writeSync('foo');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(), 'foo  ');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(true), 'foo');\n        await writeSync('bar');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(), 'fooba');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(true), 'fooba');\n        strictEqual(term.buffer.active.getLine(1)!.translateToString(true), 'r');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(false, 1), 'ooba');\n        strictEqual(term.buffer.active.getLine(0)!.translateToString(false, 1, 3), 'oo');\n      });\n\n      it('getCell', async () => {\n        term = new Terminal({ cols: 5, allowProposedApi: true });\n        strictEqual(term.buffer.active.getLine(0)!.getCell(-1), undefined);\n        strictEqual(term.buffer.active.getLine(0)!.getCell(5), undefined);\n        strictEqual(term.buffer.active.getLine(0)!.getCell(0)!.getChars(), '');\n        strictEqual(term.buffer.active.getLine(0)!.getCell(0)!.getWidth(), 1);\n        await writeSync('a文');\n        strictEqual(term.buffer.active.getLine(0)!.getCell(0)!.getChars(), 'a');\n        strictEqual(term.buffer.active.getLine(0)!.getCell(0)!.getWidth(), 1);\n        strictEqual(term.buffer.active.getLine(0)!.getCell(1)!.getChars(), '文');\n        strictEqual(term.buffer.active.getLine(0)!.getCell(1)!.getWidth(), 2);\n        strictEqual(term.buffer.active.getLine(0)!.getCell(2)!.getChars(), '');\n        strictEqual(term.buffer.active.getLine(0)!.getCell(2)!.getWidth(), 0);\n      });\n    });\n\n    it('active, normal, alternate', async () => {\n      term = new Terminal({ cols: 5, allowProposedApi: true });\n      strictEqual(term.buffer.active.type, 'normal');\n      strictEqual(term.buffer.normal.type, 'normal');\n      strictEqual(term.buffer.alternate.type, 'alternate');\n\n      await writeSync('norm ');\n      strictEqual(term.buffer.active.getLine(0)!.translateToString(), 'norm ');\n      strictEqual(term.buffer.normal.getLine(0)!.translateToString(), 'norm ');\n      strictEqual(term.buffer.alternate.getLine(0), undefined);\n\n      await writeSync('\\x1b[?47h\\r'); // use alternate screen buffer\n      strictEqual(term.buffer.active.type, 'alternate');\n      strictEqual(term.buffer.normal.type, 'normal');\n      strictEqual(term.buffer.alternate.type, 'alternate');\n\n      strictEqual(term.buffer.active.getLine(0)!.translateToString(), '     ');\n      await writeSync('alt  ');\n      strictEqual(term.buffer.active.getLine(0)!.translateToString(), 'alt  ');\n      strictEqual(term.buffer.normal.getLine(0)!.translateToString(), 'norm ');\n      strictEqual(term.buffer.alternate.getLine(0)!.translateToString(), 'alt  ');\n\n      await writeSync('\\x1b[?47l\\r'); // use normal screen buffer\n      strictEqual(term.buffer.active.type, 'normal');\n      strictEqual(term.buffer.normal.type, 'normal');\n      strictEqual(term.buffer.alternate.type, 'alternate');\n\n      strictEqual(term.buffer.active.getLine(0)!.translateToString(), 'norm ');\n      strictEqual(term.buffer.normal.getLine(0)!.translateToString(), 'norm ');\n      strictEqual(term.buffer.alternate.getLine(0), undefined);\n    });\n  });\n\n  describe('modes', () => {\n    it('defaults', () => {\n      deepStrictEqual(term.modes, {\n        applicationCursorKeysMode: false,\n        applicationKeypadMode: false,\n        bracketedPasteMode: false,\n        insertMode: false,\n        mouseTrackingMode: 'none',\n        originMode: false,\n        reverseWraparoundMode: false,\n        sendFocusMode: false,\n        showCursor: true,\n        synchronizedOutputMode: false,\n        win32InputMode: false,\n        wraparoundMode: true\n      });\n    });\n    it('applicationCursorKeysMode', async () => {\n      await writeSync('\\x1b[?1h');\n      strictEqual(term.modes.applicationCursorKeysMode, true);\n      await writeSync('\\x1b[?1l');\n      strictEqual(term.modes.applicationCursorKeysMode, false);\n    });\n    it('applicationKeypadMode', async () => {\n      await writeSync('\\x1b[?66h');\n      strictEqual(term.modes.applicationKeypadMode, true);\n      await writeSync('\\x1b[?66l');\n      strictEqual(term.modes.applicationKeypadMode, false);\n    });\n    it('bracketedPasteMode', async () => {\n      await writeSync('\\x1b[?2004h');\n      strictEqual(term.modes.bracketedPasteMode, true);\n      await writeSync('\\x1b[?2004l');\n      strictEqual(term.modes.bracketedPasteMode, false);\n    });\n    it('insertMode', async () => {\n      await writeSync('\\x1b[4h');\n      strictEqual(term.modes.insertMode, true);\n      await writeSync('\\x1b[4l');\n      strictEqual(term.modes.insertMode, false);\n    });\n    it('mouseTrackingMode', async () => {\n      await writeSync('\\x1b[?9h');\n      strictEqual(term.modes.mouseTrackingMode, 'x10');\n      await writeSync('\\x1b[?9l');\n      strictEqual(term.modes.mouseTrackingMode, 'none');\n      await writeSync('\\x1b[?1000h');\n      strictEqual(term.modes.mouseTrackingMode, 'vt200');\n      await writeSync('\\x1b[?1000l');\n      strictEqual(term.modes.mouseTrackingMode, 'none');\n      await writeSync('\\x1b[?1002h');\n      strictEqual(term.modes.mouseTrackingMode, 'drag');\n      await writeSync('\\x1b[?1002l');\n      strictEqual(term.modes.mouseTrackingMode, 'none');\n      await writeSync('\\x1b[?1003h');\n      strictEqual(term.modes.mouseTrackingMode, 'any');\n      await writeSync('\\x1b[?1003l');\n      strictEqual(term.modes.mouseTrackingMode, 'none');\n    });\n    it('originMode', async () => {\n      await writeSync('\\x1b[?6h');\n      strictEqual(term.modes.originMode, true);\n      await writeSync('\\x1b[?6l');\n      strictEqual(term.modes.originMode, false);\n    });\n    it('reverseWraparoundMode', async () => {\n      await writeSync('\\x1b[?45h');\n      strictEqual(term.modes.reverseWraparoundMode, true);\n      await writeSync('\\x1b[?45l');\n      strictEqual(term.modes.reverseWraparoundMode, false);\n    });\n    it('sendFocusMode', async () => {\n      await writeSync('\\x1b[?1004h');\n      strictEqual(term.modes.sendFocusMode, true);\n      await writeSync('\\x1b[?1004l');\n      strictEqual(term.modes.sendFocusMode, false);\n    });\n    it('wraparoundMode', async () => {\n      await writeSync('\\x1b[?7h');\n      strictEqual(term.modes.wraparoundMode, true);\n      await writeSync('\\x1b[?7l');\n      strictEqual(term.modes.wraparoundMode, false);\n    });\n  });\n\n  it('dispose', async () => {\n    term.dispose();\n    strictEqual((term as any)._core._store.isDisposed, true);\n  });\n});\n\nfunction writeSync(text: string | Uint8Array): Promise<void> {\n  return new Promise<void>(r => term.write(text, r));\n}\n\nfunction writelnSync(text: string | Uint8Array): Promise<void> {\n  return new Promise<void>(r => term.writeln(text, r));\n}\n\nfunction lineEquals(index: number, text: string): void {\n  strictEqual(term.buffer.active.getLine(index)!.translateToString(true), text);\n}\n"
  },
  {
    "path": "src/headless/public/Terminal.ts",
    "content": "/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';\nimport { ParserApi } from 'common/public/ParserApi';\nimport { UnicodeApi } from 'common/public/UnicodeApi';\nimport { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalInitOnlyOptions, IUnicodeHandling, Terminal as ITerminalApi } from '@xterm/headless';\nimport { Terminal as TerminalCore } from 'headless/Terminal';\nimport { AddonManager } from 'common/public/AddonManager';\nimport { ITerminalOptions } from 'common/Types';\nimport { Disposable } from 'common/Lifecycle';\nimport type { IEvent } from 'common/Event';\n/**\n * The set of options that only have an effect when set in the Terminal constructor.\n */\nconst CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];\n\nexport class Terminal extends Disposable implements ITerminalApi {\n  private _core: TerminalCore;\n  private _addonManager: AddonManager;\n  private _parser: IParser | undefined;\n  private _buffer: BufferNamespaceApi | undefined;\n  private _publicOptions: Required<ITerminalOptions>;\n\n  constructor(options?: ITerminalOptions & ITerminalInitOnlyOptions) {\n    super();\n\n    this._core = this._register(new TerminalCore(options));\n    this._addonManager = this._register(new AddonManager());\n\n    this._publicOptions = { ... this._core.options };\n    const getter = (propName: string): any => {\n      return this._core.options[propName];\n    };\n    const setter = (propName: string, value: any): void => {\n      this._checkReadonlyOptions(propName);\n      this._core.options[propName] = value;\n    };\n\n    for (const propName in this._core.options) {\n      Object.defineProperty(this._publicOptions, propName, {\n        get: () => {\n          return this._core.options[propName];\n        },\n        set: (value: any) => {\n          this._checkReadonlyOptions(propName);\n          this._core.options[propName] = value;\n        }\n      });\n      const desc = {\n        get: getter.bind(this, propName),\n        set: setter.bind(this, propName)\n      };\n      Object.defineProperty(this._publicOptions, propName, desc);\n    }\n  }\n\n  private _checkReadonlyOptions(propName: string): void {\n    // Throw an error if any constructor only option is modified\n    // from terminal.options\n    // Modifications from anywhere else are allowed\n    if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {\n      throw new Error(`Option \"${propName}\" can only be set in the constructor`);\n    }\n  }\n\n  private _checkProposedApi(): void {\n    if (!this._core.optionsService.options.allowProposedApi) {\n      throw new Error('You must set the allowProposedApi option to true to use proposed API');\n    }\n  }\n\n  public get onBell(): IEvent<void> { return this._core.onBell; }\n  public get onBinary(): IEvent<string> { return this._core.onBinary; }\n  public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }\n  public get onData(): IEvent<string> { return this._core.onData; }\n  public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }\n  public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }\n  public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }\n  public get onScroll(): IEvent<number> { return this._core.onScroll; }\n  public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }\n  public get onWriteParsed(): IEvent<void> { return this._core.onWriteParsed; }\n\n  public get parser(): IParser {\n    this._parser ??= new ParserApi(this._core);\n    return this._parser;\n  }\n  public get unicode(): IUnicodeHandling {\n    this._checkProposedApi();\n    return new UnicodeApi(this._core);\n  }\n  public get rows(): number { return this._core.rows; }\n  public get cols(): number { return this._core.cols; }\n  public get buffer(): IBufferNamespaceApi {\n    this._buffer ??= this._register(new BufferNamespaceApi(this._core));\n    return this._buffer;\n  }\n  public get markers(): ReadonlyArray<IMarker> {\n    return this._core.markers;\n  }\n  public get modes(): IModes {\n    const m = this._core.coreService.decPrivateModes;\n    let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';\n    switch (this._core.mouseStateService.activeProtocol) {\n      case 'X10': mouseTrackingMode = 'x10'; break;\n      case 'VT200': mouseTrackingMode = 'vt200'; break;\n      case 'DRAG': mouseTrackingMode = 'drag'; break;\n      case 'ANY': mouseTrackingMode = 'any'; break;\n    }\n    return {\n      applicationCursorKeysMode: m.applicationCursorKeys,\n      applicationKeypadMode: m.applicationKeypad,\n      bracketedPasteMode: m.bracketedPasteMode,\n      insertMode: this._core.coreService.modes.insertMode,\n      mouseTrackingMode: mouseTrackingMode,\n      originMode: m.origin,\n      reverseWraparoundMode: m.reverseWraparound,\n      sendFocusMode: m.sendFocus,\n      showCursor: !this._core.coreService.isCursorHidden,\n      synchronizedOutputMode: m.synchronizedOutput,\n      win32InputMode: m.win32InputMode,\n      wraparoundMode: m.wraparound\n    };\n  }\n  public get options(): Required<ITerminalOptions> {\n    return this._publicOptions;\n  }\n  public set options(options: ITerminalOptions) {\n    for (const propName in options) {\n      this._publicOptions[propName] = options[propName];\n    }\n  }\n  public input(data: string, wasUserInput: boolean = true): void {\n    this._core.input(data, wasUserInput);\n  }\n  public resize(columns: number, rows: number): void {\n    this._verifyIntegers(columns, rows);\n    this._core.resize(columns, rows);\n  }\n  public registerMarker(cursorYOffset: number = 0): IMarker | undefined {\n    this._verifyIntegers(cursorYOffset);\n    return this._core.addMarker(cursorYOffset);\n  }\n  public addMarker(cursorYOffset: number): IMarker | undefined {\n    return this.registerMarker(cursorYOffset);\n  }\n  public dispose(): void {\n    super.dispose();\n  }\n  public scrollLines(amount: number): void {\n    this._verifyIntegers(amount);\n    this._core.scrollLines(amount);\n  }\n  public scrollPages(pageCount: number): void {\n    this._verifyIntegers(pageCount);\n    this._core.scrollPages(pageCount);\n  }\n  public scrollToTop(): void {\n    this._core.scrollToTop();\n  }\n  public scrollToBottom(): void {\n    this._core.scrollToBottom();\n  }\n  public scrollToLine(line: number): void {\n    this._verifyIntegers(line);\n    this._core.scrollToLine(line);\n  }\n  public clear(): void {\n    this._core.clear();\n  }\n  public write(data: string | Uint8Array, callback?: () => void): void {\n    this._core.write(data, callback);\n  }\n  public writeln(data: string | Uint8Array, callback?: () => void): void {\n    this._core.write(data);\n    this._core.write('\\r\\n', callback);\n  }\n  public reset(): void {\n    this._core.reset();\n  }\n  public loadAddon(addon: ITerminalAddon): void {\n    // TODO: This could cause issues if the addon calls renderer apis\n    this._addonManager.loadAddon(this as any, addon);\n  }\n\n  private _verifyIntegers(...values: number[]): void {\n    for (const value of values) {\n      if (value === Infinity || isNaN(value) || value % 1 !== 0) {\n        throw new Error('This API only accepts integers');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/headless/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig-library-base\",\n  \"compilerOptions\": {\n    \"lib\": [\n      \"es2015\",\n      \"es2016.Array.Include\"\n    ],\n    \"outDir\": \"../../out\",\n    \"types\": [\n      \"../../node_modules/@types/mocha\",\n      \"../../node_modules/@types/node\"\n    ],\n    \"paths\": {\n      \"common/*\": [ \"./../common/*\" ],\n      \"*\": [ \"./../*\" ]\n    }\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../typings/xterm.d.ts\", // common/Types.d.ts imports from '@xterm/xterm'\n    \"../../typings/xterm-headless.d.ts\"\n  ],\n  \"references\": [\n    { \"path\": \"../common\" }\n  ]\n}\n"
  },
  {
    "path": "src/tsconfig-base.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2021\",\n    \"rootDir\": \".\",\n\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"pretty\": true,\n\n    \"incremental\": true,\n    \"experimentalDecorators\": true\n  }\n}\n"
  },
  {
    "path": "src/tsconfig-library-base.json",
    "content": "{\n  \"extends\": \"./tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"strict\": true,\n    \"declarationMap\": true,\n    \"experimentalDecorators\": true,\n    \"downlevelIteration\": true\n  }\n}\n"
  },
  {
    "path": "test/benchmark/EscapeSequenceParser.benchmark.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-benchmark';\n\nimport { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';\nimport { C0, C1 } from 'common/data/EscapeSequences';\nimport { IDcsHandler, IOscHandler, IParams } from 'common/parser/Types';\nimport { OscHandler } from 'common/parser/OscParser';\nimport { DcsHandler } from '../../out/common/parser/DcsParser';\n\nconst SIZE = 5000000;\n\nfunction toUtf32(s: string): Uint32Array {\n  const result = new Uint32Array(s.length);\n  for (let i = 0; i < s.length; ++i) {\n    result[i] = s.charCodeAt(i);\n  }\n  return result;\n}\n\nclass FastDcsHandler implements IDcsHandler {\n  public hook(params: IParams): void {}\n  public put(data: Uint32Array, start: number, end: number): void {}\n  public unhook(success: boolean): boolean { return true; }\n}\n\nclass FastOscHandler implements IOscHandler {\n  public start(): void {}\n  public put(data: Uint32Array, start: number, end: number): void {}\n  public end(success: boolean): boolean { return true; }\n}\n\n\nperfContext('Parser throughput - 50MB data', () => {\n  let parsed: Uint32Array;\n  let parser: EscapeSequenceParser;\n\n  beforeEach(() => {\n    parser = new EscapeSequenceParser();\n    parser.setPrintHandler((data, start, end) => {});\n    parser.registerCsiHandler({ final: '@' }, params => true);\n    parser.registerCsiHandler({ final: 'A' }, params => true);\n    parser.registerCsiHandler({ final: 'B' }, params => true);\n    parser.registerCsiHandler({ final: 'C' }, params => true);\n    parser.registerCsiHandler({ final: 'D' }, params => true);\n    parser.registerCsiHandler({ final: 'E' }, params => true);\n    parser.registerCsiHandler({ final: 'F' }, params => true);\n    parser.registerCsiHandler({ final: 'G' }, params => true);\n    parser.registerCsiHandler({ final: 'H' }, params => true);\n    parser.registerCsiHandler({ final: 'I' }, params => true);\n    parser.registerCsiHandler({ final: 'J' }, params => true);\n    parser.registerCsiHandler({ final: 'K' }, params => true);\n    parser.registerCsiHandler({ final: 'L' }, params => true);\n    parser.registerCsiHandler({ final: 'M' }, params => true);\n    parser.registerCsiHandler({ final: 'P' }, params => true);\n    parser.registerCsiHandler({ final: 'S' }, params => true);\n    parser.registerCsiHandler({ final: 'T' }, params => true);\n    parser.registerCsiHandler({ final: 'X' }, params => true);\n    parser.registerCsiHandler({ final: 'Z' }, params => true);\n    parser.registerCsiHandler({ final: '`' }, params => true);\n    parser.registerCsiHandler({ final: 'a' }, params => true);\n    parser.registerCsiHandler({ final: 'b' }, params => true);\n    parser.registerCsiHandler({ final: 'c' }, params => true);\n    parser.registerCsiHandler({ final: 'd' }, params => true);\n    parser.registerCsiHandler({ final: 'e' }, params => true);\n    parser.registerCsiHandler({ final: 'f' }, params => true);\n    parser.registerCsiHandler({ final: 'g' }, params => true);\n    parser.registerCsiHandler({ final: 'h' }, params => true);\n    parser.registerCsiHandler({ final: 'l' }, params => true);\n    parser.registerCsiHandler({ final: 'm' }, params => true);\n    parser.registerCsiHandler({ final: 'n' }, params => true);\n    parser.registerCsiHandler({ final: 'p' }, params => true);\n    parser.registerCsiHandler({ final: 'q' }, params => true);\n    parser.registerCsiHandler({ final: 'r' }, params => true);\n    parser.registerCsiHandler({ final: 's' }, params => true);\n    parser.registerCsiHandler({ final: 'u' }, params => true);\n    parser.setExecuteHandler(C0.BEL, () => true);\n    parser.setExecuteHandler(C0.LF, () => true);\n    parser.setExecuteHandler(C0.VT, () => true);\n    parser.setExecuteHandler(C0.FF, () => true);\n    parser.setExecuteHandler(C0.CR, () => true);\n    parser.setExecuteHandler(C0.BS, () => true);\n    parser.setExecuteHandler(C0.HT, () => true);\n    parser.setExecuteHandler(C0.SO, () => true);\n    parser.setExecuteHandler(C0.SI, () => true);\n    parser.setExecuteHandler(C1.IND, () => true);\n    parser.setExecuteHandler(C1.NEL, () => true);\n    parser.setExecuteHandler(C1.HTS, () => true);\n    parser.registerOscHandler(0, new OscHandler(data => true));\n    parser.registerOscHandler(1, new FastOscHandler());\n    parser.registerEscHandler({ final: '7' }, () => true);\n    parser.registerEscHandler({ final: '8' }, () => true);\n    parser.registerEscHandler({ final: 'D' }, () => true);\n    parser.registerEscHandler({ final: 'E' }, () => true);\n    parser.registerEscHandler({ final: 'H' }, () => true);\n    parser.registerEscHandler({ final: 'M' }, () => true);\n    parser.registerEscHandler({ final: '=' }, () => true);\n    parser.registerEscHandler({ final: '>' }, () => true);\n    parser.registerEscHandler({ final: 'c' }, () => true);\n    parser.registerEscHandler({ final: 'n' }, () => true);\n    parser.registerEscHandler({ final: 'o' }, () => true);\n    parser.registerEscHandler({ final: '|' }, () => true);\n    parser.registerEscHandler({ final: '}' }, () => true);\n    parser.registerEscHandler({ final: '~' }, () => true);\n    parser.registerEscHandler({ intermediates: '%', final: '@' }, () => true);\n    parser.registerEscHandler({ intermediates: '%', final: 'G' }, () => true);\n    parser.registerDcsHandler({ final: 'p' }, new DcsHandler(data => true));\n    parser.registerDcsHandler({ final: 'q' }, new FastDcsHandler());\n  });\n\n  perfContext('PRINT - a', () => {\n    before(() => {\n      const data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', async () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('EXECUTE - \\\\n', () => {\n    before(() => {\n      const data = '\\n\\n\\n\\n\\n\\n\\n';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('ESCAPE - ESC E', () => {\n    before(() => {\n      const data = '\\x1bE\\x1bE\\x1bE\\x1bE\\x1bE\\x1bE\\x1bE\\x1bE\\x1bE\\x1bE';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('ESCAPE with collect - ESC % G', () => {\n    before(() => {\n      const data = '\\x1b%G\\x1b%G\\x1b%G\\x1b%G\\x1b%G\\x1b%G\\x1b%G\\x1b%G\\x1b%G\\x1b%G';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('CSI - CSI A', () => {\n    before(() => {\n      const data = '\\x1b[A\\x1b[A\\x1b[A\\x1b[A\\x1b[A\\x1b[A\\x1b[A\\x1b[A\\x1b[A\\x1b[A';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('CSI with collect - CSI ? p', () => {\n    before(() => {\n      const data = '\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p\\x1b[?p';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('CSI with params (short) - CSI 1;2 m', () => {\n    before(() => {\n      const data = '\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m\\x1b[1;2m';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('CSI with params (long) - CSI 1;2;3;4;5;6;7;8;9;0 m', () => {\n    before(() => {\n      const data = '\\x1b[1;2;3;4;5;6;7;8;9;0m\\x1b[1;2;3;4;5;6;7;8;9;0m\\x1b[1;2;3;4;5;6;7;8;9;0m';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('OSC string interface (short seq) - OSC 0;hi ST', () => {\n    before(() => {\n      const data = '\\x1b]0;hi\\x1b\\\\\\x1b]0;hi\\x1b\\\\\\x1b]0;hi\\x1b\\\\\\x1b]0;hi\\x1b\\\\x1b]0;hi\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('OSC string interface (long seq) - OSC 0;<text> ST', () => {\n    before(() => {\n      const data = '\\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('OSC class interface (short seq) - OSC 0;hi ST', () => {\n    before(() => {\n      const data = '\\x1b]1;hi\\x1b\\\\\\x1b]1;hi\\x1b\\\\\\x1b]1;hi\\x1b\\\\\\x1b]1;hi\\x1b\\\\x1b]1;hi\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('OSC class interface (long seq) - OSC 0;<text> ST', () => {\n    before(() => {\n      const data = '\\x1b]1;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('DCS string interface (short seq)', () => {\n    before(() => {\n      const data = '\\x1bPphi\\x1b\\\\\\x1bPphi\\x1b\\\\\\x1bPphi\\x1b\\\\\\x1bPphi\\x1b\\\\\\x1bPphi\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', async () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('DCS string interface (long seq)', () => {\n    before(() => {\n      const data = '\\x1bPpLorem ipsum dolor sit amet, consetetur sadipscing elitr.\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', async () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('DCS class interface (short seq)', () => {\n    before(() => {\n      const data = '\\x1bPqhi\\x1b\\\\\\x1bPqhi\\x1b\\\\\\x1bPqhi\\x1b\\\\\\x1bPqhi\\x1b\\\\\\x1bPqhi\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', async () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n\n  perfContext('DCS class interface (long seq)', () => {\n    before(() => {\n      const data = '\\x1bPqLorem ipsum dolor sit amet, consetetur sadipscing elitr.\\x1b\\\\';\n      let content = '';\n      while (content.length < SIZE) {\n        content += data;\n      }\n      parsed = toUtf32(content);\n    });\n    new ThroughputRuntimeCase('', async () => {\n      parser.parse(parsed, parsed.length);\n      return { payloadSize: parsed.length };\n    }, { fork: true }).showAverageThroughput();\n  });\n});\n"
  },
  {
    "path": "test/benchmark/Event.benchmark.ts",
    "content": "/**\n * Copyright (c) 2026 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { perfContext, before, RuntimeCase } from 'xterm-benchmark';\nimport { Emitter } from 'common/Event';\n\nconst ITERATIONS = 1_000_000;\n\nperfContext('Emitter.fire()', () => {\n  perfContext('0 listeners', () => {\n    let emitter: Emitter<number>;\n    before(() => {\n      emitter = new Emitter<number>();\n    });\n    new RuntimeCase('', () => {\n      for (let i = 0; i < ITERATIONS; i++) {\n        emitter.fire(i);\n      }\n      return { payloadSize: ITERATIONS };\n    }, { fork: false }).showAverageRuntime();\n  });\n\n  perfContext('1 listener', () => {\n    let emitter: Emitter<number>;\n    let sum = 0;\n    before(() => {\n      emitter = new Emitter<number>();\n      emitter.event(e => { sum += e; });\n    });\n    new RuntimeCase('', () => {\n      for (let i = 0; i < ITERATIONS; i++) {\n        emitter.fire(i);\n      }\n      return { payloadSize: ITERATIONS, sum };\n    }, { fork: false }).showAverageRuntime();\n  });\n\n  perfContext('2 listeners', () => {\n    let emitter: Emitter<number>;\n    let sum = 0;\n    before(() => {\n      emitter = new Emitter<number>();\n      emitter.event(e => { sum += e; });\n      emitter.event(e => { sum += e * 2; });\n    });\n    new RuntimeCase('', () => {\n      for (let i = 0; i < ITERATIONS; i++) {\n        emitter.fire(i);\n      }\n      return { payloadSize: ITERATIONS, sum };\n    }, { fork: false }).showAverageRuntime();\n  });\n\n  perfContext('5 listeners', () => {\n    let emitter: Emitter<number>;\n    let sum = 0;\n    before(() => {\n      emitter = new Emitter<number>();\n      for (let j = 0; j < 5; j++) {\n        emitter.event(e => { sum += e; });\n      }\n    });\n    new RuntimeCase('', () => {\n      for (let i = 0; i < ITERATIONS; i++) {\n        emitter.fire(i);\n      }\n      return { payloadSize: ITERATIONS, sum };\n    }, { fork: false }).showAverageRuntime();\n  });\n});\n"
  },
  {
    "path": "test/benchmark/Terminal.benchmark.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';\n\nimport { spawn } from 'node-pty';\nimport { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';\nimport { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';\n\nperfContext('Terminal: ls -lR /usr/lib', () => {\n  let content = '';\n  let contentUtf8: Uint8Array;\n\n  before(async () => {\n    // grab output from \"ls -lR /usr\"\n    const p = spawn('ls', ['--color=auto', '-lR', '/usr/lib'], {\n      name: 'xterm-256color',\n      cols: 80,\n      rows: 25,\n      cwd: process.env.HOME,\n      env: process.env,\n      encoding: (null as unknown as string) // needs to be fixed in node-pty\n    });\n    const chunks: Buffer[] = [];\n    let length = 0;\n    p.onData(data => {\n      chunks.push(data as unknown as Buffer);\n      length += data.length;\n    });\n    await new Promise<void>(resolve => p.onExit(() => resolve()));\n    contentUtf8 = Buffer.concat(chunks, length);\n    // translate to content string\n    const buffer = new Uint32Array(contentUtf8.length);\n    const decoder = new Utf8ToUtf32();\n    const codepoints = decoder.decode(contentUtf8, buffer);\n    for (let i = 0; i < codepoints; ++i) {\n      content += stringFromCodePoint(buffer[i]);\n      // peek into content to force flat repr in v8\n      if (!(i % 10000000)) {\n        content[i];\n      }\n    }\n  });\n\n  perfContext('write/string/async', () => {\n    let terminal: CoreBrowserTerminal;\n    before(() => {\n      terminal = new CoreBrowserTerminal({ cols: 80, rows: 25, scrollback: 1000 });\n    });\n    new ThroughputRuntimeCase('', async () => {\n      await new Promise<void>(res => terminal.write(content, res));\n      return { payloadSize: contentUtf8.length };\n    }, { fork: false }).showAverageThroughput();\n  });\n\n  perfContext('write/Utf8/async', () => {\n    let terminal: CoreBrowserTerminal;\n    before(() => {\n      terminal = new CoreBrowserTerminal({ cols: 80, rows: 25, scrollback: 1000 });\n    });\n    new ThroughputRuntimeCase('', async () => {\n      await new Promise<void>(res => terminal.write(content, res));\n      return { payloadSize: contentUtf8.length };\n    }, { fork: false }).showAverageThroughput();\n  });\n});\n"
  },
  {
    "path": "test/benchmark/benchmark.json",
    "content": "{\n  \"APP_PATH\": \".benchmark\",\n  \"evalConfig\": {\n    \"tolerance\": {\n      \"*\": [0.75, 1.5],\n      \"*.dev\": [0.01, 1.5],\n      \"*.cv\": [0.01, 1.5],\n      \"EscapeSequenceParser.benchmark.js.*.averageThroughput.mean\": [0.9, 5]\n    },\n    \"skip\": [\n      \"*.median\",\n      \"*.runs\",\n      \"*.dev\",\n      \"*.cv\",\n      \"EscapeSequenceParser.benchmark.js.*.averageRuntime\",\n      \"Terminal.benchmark.js.*.averageRuntime\"\n    ]\n  } \n}\n"
  },
  {
    "path": "test/benchmark/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\n      \"dom\",\n      \"es2021\",\n    ],\n    \"outDir\": \"../../out-test/benchmark\",\n    \"types\": [\n      \"../../node_modules/@types/node\"\n    ],\n    \"moduleResolution\": \"node\",\n    \"strict\": false,\n    \"target\": \"es2021\",\n    \"module\": \"commonjs\",\n    \"paths\": {\n      \"common/*\": [ \"../../src/common/*\" ],\n      \"browser/*\": [ \"../../src/browser/*\" ],\n      \"Terminal\": [\"../../src/Terminal\"]\n    },\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../typings/xterm.d.ts\"\n  ],\n  \"exclude\": [\n    \"../../**/*test.ts\"\n  ],\n  \"references\": [\n    { \"path\": \"../../src/common\" },\n    { \"path\": \"../../src/browser\" },\n  ]\n}\n"
  },
  {
    "path": "test/playwright/CharWidth.test.ts",
    "content": "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { test } from '@playwright/test';\nimport { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.beforeEach(async () => await ctx.proxy.reset());\n\ntest.describe('CharWidth Integration Tests', () => {\n  test.describe('getStringCellWidth', () => {\n    test('ASCII chars', async () => {\n      await ctx.proxy.write('This is just ASCII text.#');\n      await pollFor(ctx.page, () => sumWidths(0, 1, '#'), 25);\n    });\n\n    test('combining chars', async () => {\n      await ctx.proxy.write('e\\u0301e\\u0301e\\u0301e\\u0301e\\u0301e\\u0301e\\u0301e\\u0301e\\u0301#');\n      await pollFor(ctx.page, () => sumWidths(0, 1, '#'), 10);\n    });\n\n    test('surrogate chars', async () => {\n      await ctx.proxy.write('𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞#');\n      await pollFor(ctx.page, () => sumWidths(0, 1, '#'), 28);\n    });\n\n    test('surrogate combining chars', async () => {\n      await ctx.proxy.write('𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301𓂀\\u0301#');\n      await pollFor(ctx.page, () => sumWidths(0, 1, '#'), 12);\n    });\n\n    test('fullwidth chars', async () => {\n      await ctx.proxy.write('１２３４５６７８９０#');\n      await pollFor(ctx.page, () => sumWidths(0, 1, '#'), 21);\n    });\n\n    test('fullwidth chars offset 1', async () => {\n      await ctx.proxy.write('a１２３４５６７８９０#');\n      await pollFor(ctx.page, () => sumWidths(0, 1, '#'), 22);\n    });\n\n    // TODO: multiline tests once #1685 is resolved\n  });\n});\n\nasync function sumWidths(start: number, end: number, sentinel: string): Promise<number> {\n  await ctx.page.evaluate(`\n    (function() {\n      window.result = 0;\n      const buffer = window.term.buffer.active;\n      for (let i = ${start}; i < ${end}; i++) {\n        const line = buffer.getLine(i);\n        let j = 0;\n        while (true) {\n          const cell = line.getCell(j++);\n          if (!cell) {\n            break;\n          }\n          window.result += cell.getWidth();\n          if (cell.getChars() === '${sentinel}') {\n            return;\n          }\n        }\n      }\n    })();\n  `);\n  return await ctx.page.evaluate(`window.result`);\n}\n"
  },
  {
    "path": "test/playwright/InputHandler.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { test } from '@playwright/test';\nimport { deepStrictEqual, ok } from 'assert';\nimport { ITestContext, createTestContext, openTerminal, pollFor } from './TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\n\ntest.describe('InputHandler Integration Tests', () => {\n  let recordedData: string[];\n  let recordDataDisposable: { dispose: () => void };\n  test.beforeAll(async () => {\n    recordedData = [];\n    recordDataDisposable = ctx.proxy.onData(d => recordedData.push(d));\n  });\n  test.afterAll(async () => {\n    recordDataDisposable.dispose();\n  });\n  test.beforeEach(async () => {\n    recordedData.length = 0;\n    await ctx.proxy.resize(80, 24);\n  });\n\n  test.describe('CSI', () => {\n    test.beforeEach(async () => await ctx.proxy.reset());\n\n    test('CSI Ps @ - ICH: Insert Ps (Blank) Character(s) (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[3D\\x1b[@\\n\\r');\n      // Explicit\n      await ctx.proxy.write('bar\\x1b[3D\\x1b[4@');\n      await pollFor(ctx.page, () => getLinesAsArray(2), [' foo', '    bar']);\n    });\n    test('CSI Ps SP @ - SL: Shift left Ps columns(s) (default = 1), ECMA-48', async () => {\n      // Default\n      await ctx.proxy.write('abcdefg\\x1b[ @');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['bcdefg']);\n      // Explicit\n      await ctx.proxy.reset();\n      await ctx.proxy.write('abcdefg\\x1b[3 @');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['defg']);\n    });\n    test('CSI Ps A - CUU: Cursor Up Ps Times (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\n\\n\\n\\n\\x1b[Aa');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Ab');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['', ' b', '', 'a']);\n    });\n    test('CSI Ps B - CUD: Cursor Down Ps Times (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\x1b[Ba');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Bb');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['', 'a', '', ' b']);\n    });\n    test('CSI Ps C - CUF: Cursor Forward Ps Times (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\x1b[Ca');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Cb');\n      await pollFor(ctx.page, () => getLinesAsArray(1), [' a  b']);\n    });\n    test('CSI Ps D - CUB: Cursor Backward Ps Times (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[Da');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Db');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['fba']);\n    });\n    test('CSI Ps E - CNL: Cursor Next Line Ps Times (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\x1b[Ea');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Eb');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['', 'a', '', 'b']);\n    });\n    test('CSI Ps F - CPL: Cursor Preceding Line Ps Times (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\n\\n\\n\\n\\x1b[Fa');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Fb');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['', 'b', '', 'a', '']);\n    });\n    test('CSI Ps G - CHA: Cursor Character Absolute [column] (default = [row,1])', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[Ga');\n      // Explicit\n      await ctx.proxy.write('\\x1b[10Gb');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['aoo      b']);\n    });\n    test('CSI Ps ; Ps H - CUP: Cursor Position [row;column] (default = [1,1])', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[Ha');\n      // Explicit\n      await ctx.proxy.write('\\x1b[3;3Hb');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['aoo', '', '  b']);\n    });\n    test('CSI Ps I - CHT: Cursor Forward Tabulation Ps tab stops (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\x1b[Ia');\n      // Explicit\n      await ctx.proxy.write('\\n\\r\\x1b[2Ib');\n      await pollFor(ctx.page, () => getLinesAsArray(2), ['        a', '                b']);\n    });\n    test('CSI Ps J - ED: Erase in Display, VT100', async () => {\n      const fixture = 'abc\\n\\rdef\\n\\rghi\\x1b[2;2H';\n      // Default: Erase Below\n      await ctx.proxy.resize(5, 5);\n      await ctx.proxy.write(fixture + '\\x1b[J');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['abc', 'd', '']);\n      // 0: Erase Below\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[0J');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['abc', 'd', '']);\n      // 1: Erase Above\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[1J');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['', '  f', 'ghi']);\n      // 2: Erase Saved Lines (scrollback)\n      await ctx.proxy.reset();\n      await ctx.proxy.write('1\\n2\\n3\\n4\\n5' + fixture + '\\x1b[3J');\n      await pollFor(ctx.page, () => ctx.proxy.buffer.active.length, 5);\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['   4', '    5', 'abc', 'def', 'ghi']);\n    });\n    test('CSI ? Ps J - DECSED: Erase in Display, VT220', async () => {\n      const fixture = 'abc\\n\\rdef\\n\\rghi\\x1b[2;2H';\n      // Default: Erase Below\n      await ctx.proxy.resize(5, 5);\n      await ctx.proxy.write(fixture + '\\x1b[?J');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['abc', 'd', '']);\n      // 0: Erase Below\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[?0J');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['abc', 'd', '']);\n      // 1: Erase Above\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[?1J');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['', '  f', 'ghi']);\n      // 2: Erase Saved Lines (scrollback)\n      await ctx.proxy.reset();\n      await ctx.proxy.write('1\\n2\\n3\\n4\\n5' + fixture + '\\x1b[?3J');\n      await pollFor(ctx.page, () => ctx.proxy.buffer.active.length, 5);\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['   4', '    5', 'abc', 'def', 'ghi']);\n    });\n    test('CSI Ps K - EL: Erase in Line, VT100', async () => {\n      const fixture = 'abcde\\x1b[1;3H';\n      // Default: Erase to Right\n      await ctx.proxy.write(fixture + '\\x1b[K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['ab']);\n      // 0: Erase to Right\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[0K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['ab']);\n      // 1: Erase to Left\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[1K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['   de']);\n      // 2: Erase All\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[2K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['']);\n    });\n    test('CSI ? Ps K - DECSEL: Erase in Line, VT220', async () => {\n      const fixture = 'abcde\\x1b[1;3H';\n      // Default: Erase to Right\n      await ctx.proxy.write(fixture + '\\x1b[?K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['ab']);\n      // 0: Erase to Right\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[?0K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['ab']);\n      // 1: Erase to Left\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[?1K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['   de']);\n      // 2: Erase All\n      await ctx.proxy.reset();\n      await ctx.proxy.write(fixture + '\\x1b[?2K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['']);\n    });\n    test('CSI Ps L - IL: Insert Ps Line(s) (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[La');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2Lb');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['b', '', 'a', 'foo']);\n    });\n    test('CSI Ps M - DL: Delete Ps Line(s) (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('a\\nb\\x1b[1F\\x1b[M');\n      // Explicit\n      await ctx.proxy.write('\\x1b[1Ed\\ne\\nf\\x1b[2F\\x1b[2M');\n      await pollFor(ctx.page, () => getLinesAsArray(5), [' b', '  f', '', '', '']);\n    });\n    test('CSI Ps P - DCH: Delete Ps Character(s) (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('abc\\x1b[1;1H\\x1b[P');\n      // Explicit\n      await ctx.proxy.write('\\n\\rdef\\x1b[2;1H\\x1b[2P');\n      await pollFor(ctx.page, () => getLinesAsArray(2), ['bc', 'f']);\n    });\n    test.skip('CSI Pm # P - XTPUSHCOLORS: Push current dynamic- and ANSI-palette colors onto stack, xterm', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pm # Q - XTPOPCOLORS: Pop stack to set dynamic- and ANSI-palette colors, xterm', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI # R - XTREPORTCOLORS: Report the current entry on the palette stack, and the number of palettes stored on the stack, using the same form as XTPOPCOLOR (default = 0), xterm', async () => {\n      // TODO: Implement\n    });\n    test('CSI Ps S - SU: Scroll up Ps lines (default = 1), VT420, ECMA-48', async () => {\n      await ctx.proxy.write('1\\r\\n2\\r\\n3\\r\\n4\\r\\n5');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['1', '2', '3', '4', '5']);\n      await ctx.proxy.write('\\x1b[S');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['2', '3', '4', '5', '']);\n      await ctx.proxy.reset();\n      await ctx.proxy.write('1\\r\\n2\\r\\n3\\r\\n4\\r\\n5');\n      await ctx.proxy.write('\\x1b[2S');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['3', '4', '5', '', '']);\n    });\n    // This is intentionally not implemented here are image support lives within an addon\n    // test.skip('CSI ? Pi ; Pa ; Pv S - XTSMGRAPHICS: Set or request graphics attribute, xterm', async () => {\n    // });\n    test('CSI Ps T - SD: Scroll down Ps lines (default = 1), VT420', async () => {\n      await ctx.proxy.write('1\\r\\n2\\r\\n3\\r\\n4\\r\\n5');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['1', '2', '3', '4', '5']);\n      await ctx.proxy.write('\\x1b[T');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['', '1', '2', '3', '4']);\n      await ctx.proxy.reset();\n      await ctx.proxy.write('1\\r\\n2\\r\\n3\\r\\n4\\r\\n5');\n      await ctx.proxy.write('\\x1b[2T');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['', '', '1', '2', '3']);\n    });\n    test.skip('CSI Ps ; Ps ; Ps ; Ps ; Ps T - XTHIMOUSE: Initiate highlight mouse tracking (XTHIMOUSE), xterm', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI > Pm T - XTRMTITLE: Reset title mode features to default value, xterm', async () => {\n      // TODO: Implement\n    });\n    test('CSI Ps X - ECH: Erase Ps Character(s) (default = 1)', async () => {\n      await ctx.proxy.write('abcdef\\x1b[1;1H\\x1b[X');\n      await pollFor(ctx.page, () => getLinesAsArray(1), [' bcdef']);\n      await ctx.proxy.reset();\n      await ctx.proxy.write('abcdef\\x1b[1;1H\\x1b[3X');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['   def']);\n    });\n    test('CSI Ps Z - CBT: Cursor Backward Tabulation Ps tab stops (default = 1)', async () => {\n      await ctx.proxy.write('\\x1b[17Ga\\x1b[17G\\x1b[Zb');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['        b       a']);\n    });\n    test('CSI Ps ^ - SD: Scroll down Ps lines (default = 1) (SD), ECMA-48', async () => {\n      await ctx.proxy.write('1\\r\\n2\\r\\n3\\r\\n4\\r\\n5');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['1', '2', '3', '4', '5']);\n      await ctx.proxy.write('\\x1b[^');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['', '1', '2', '3', '4']);\n      await ctx.proxy.reset();\n      await ctx.proxy.write('1\\r\\n2\\r\\n3\\r\\n4\\r\\n5');\n      await ctx.proxy.write('\\x1b[2^');\n      await pollFor(ctx.page, () => getLinesAsArray(5), ['', '', '1', '2', '3']);\n    });\n    test('CSI Ps ` - HPA: Character Position Absolute [column] (default = [row,1])', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[`a');\n      // Explicit\n      await ctx.proxy.write('\\x1b[10`b');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['aoo      b']);\n    });\n    test('CSI Ps a - HPR: Character Position Relative (default = [row,col+1])', async () => {\n      await ctx.proxy.write('a\\x1b[2aB');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['a  B']);\n    });\n    test('CSI Ps b - REP: Repeat preceding character, ECMA48', async () => {\n      // default to 1\n      await ctx.proxy.resize(10, 10);\n      await ctx.proxy.write('#\\x1b[b');\n      await ctx.proxy.writeln('');\n      await ctx.proxy.write('#\\x1b[0b');\n      await ctx.proxy.writeln('');\n      await ctx.proxy.write('#\\x1b[1b');\n      await ctx.proxy.writeln('');\n      await ctx.proxy.write('#\\x1b[5b');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['##', '##', '##', '######']);\n      await pollFor(ctx.page, () => getCursor(), { col: 6, row: 3 });\n      // repeat on fullwidth chars\n      await ctx.proxy.reset();\n      await ctx.proxy.write('￥\\x1b[8b');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['￥￥￥￥￥']);\n      // change from xterm: repeat grapheme cluster\n      await ctx.proxy.reset();\n      await ctx.proxy.write('e\\u0301\\x1b[2b');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['e\\u0301e\\u0301e\\u0301']);\n      // should wrap correctly\n      await ctx.proxy.reset();\n      await ctx.proxy.write('#\\x1b[15b');\n      await pollFor(ctx.page, () => getLinesAsArray(2), ['##########', '######']);\n      // disable wrap around\n      await ctx.proxy.reset();\n      await ctx.proxy.write('\\x1b[?7l');\n      await ctx.proxy.write('#\\x1b[15b');\n      await pollFor(ctx.page, () => getLinesAsArray(2), ['##########', '']);\n      // any successful sequence should reset REP\n      await ctx.proxy.reset();\n      await ctx.proxy.write('\\x1b[?7h');  // re-enable wrap around\n      await ctx.proxy.write('#\\n\\x1b[3b');\n      await ctx.proxy.write('#\\r\\x1b[3b');\n      await ctx.proxy.writeln('');\n      await ctx.proxy.write('abcdefg\\x1b[3D\\x1b[10b#\\x1b[3b');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']);\n    });\n    test('CSI Ps c - ', async () => {\n      await ctx.proxy.write('\\x1b[c');\n      await pollFor(ctx.page, () => recordedData, ['\\x1b[?1;2c']);\n    });\n    test.skip('CSI = Ps c - ', async () => {\n      // TODO: Implement\n    });\n    test('CSI > Ps c - ', async () => {\n      await ctx.proxy.write('\\x1b[>c');\n      await pollFor(ctx.page, () => recordedData, ['\\x1b[>0;276;0c']);\n    });\n    test('CSI Ps d - VPA: Line Position Absolute [row] (default = [1,column])', async () => {\n      // Default\n      await ctx.proxy.write('\\n\\n\\n   \\x1b[da');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2d    b');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['   a', '        b', '', '   ']);\n    });\n    test('CSI Ps e - VPR: Line Position Relative (default = 1)', async () => {\n      // Default\n      await ctx.proxy.write('\\x1b[ea');\n      // Explicit\n      await ctx.proxy.write('\\x1b[2eb');\n      await pollFor(ctx.page, () => getLinesAsArray(4), ['', 'a', '', ' b']);\n    });\n    test('CSI Ps ; Ps f - HVP: Horizontal and Vertical Position [row;column] (default = [1,1])', async () => {\n      // Default\n      await ctx.proxy.write('foo\\x1b[fa');\n      // Explicit\n      await ctx.proxy.write('\\x1b[3;3fb');\n      await pollFor(ctx.page, () => getLinesAsArray(3), ['aoo', '', '  b']);\n    });\n    test('CSI Ps g - TBC: Tab Clear (default = 0)', async () => {\n      // Default: Clear tab stop at cursor position\n      // Move to column 9 (first tab stop), clear it, go back to column 1, tab should skip to column 17\n      await ctx.proxy.write('\\x1b[9G\\x1b[g\\x1b[1G\\ta');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['                a']);\n      // Ps=3: Clear all tab stops\n      await ctx.proxy.reset();\n      await ctx.proxy.write('\\x1b[3g\\ta');\n      // With all tabs cleared, tab moves to end of line\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['                                                                               a']);\n    });\n    test('CSI Ps h - SM: Set Mode', async () => {\n      await ctx.proxy.write('\\x1b[4h');\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).insertMode, true);\n      await ctx.proxy.write('\\x1b[20h');\n      await pollFor(ctx.page, async () => await ctx.proxy.getOption('convertEol'), true);\n    });\n    test.describe('CSI ? Pm h - DECSET: Private Mode Set', () => {\n      test('Ps = 1 - Application Cursor Keys (DECCKM), VT100', async () => {\n        await ctx.proxy.write('\\x1b[?1h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationCursorKeysMode, true);\n        recordedData.length = 0;\n        await ctx.proxy.focus();\n        await ctx.page.keyboard.press('ArrowUp');\n        await pollFor(ctx.page, () => recordedData, ['\\x1bOA']);\n      });\n      test('Ps = 2 - Designate USASCII for character sets G0-G3 (DECANM), VT100, and set VT100 mode', async () => {\n        await ctx.proxy.write('\\x1b(0q\\x1b[?2hq');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['─q']);\n      });\n      test('Ps = 3 - 132 Column Mode (DECCOLM), VT100', async () => {\n        const windowOptions = await ctx.proxy.getOption('windowOptions');\n        await ctx.proxy.setOption('windowOptions', { ...windowOptions, setWinLines: true });\n        await ctx.proxy.write('\\x1b[?3h');\n        await pollFor(ctx.page, async () => await ctx.proxy.cols, 132);\n        await ctx.proxy.resize(80, 24);\n        await ctx.proxy.setOption('windowOptions', windowOptions);\n      });\n      test('Ps = 4 - Smooth (Slow) Scroll (DECSCLM), VT100', async () => {\n        await assertNoModeChange('\\x1b[?4h');\n      });\n      test('Ps = 5 - Reverse Video (DECSCNM), VT100', async () => {\n        await assertNoModeChange('\\x1b[?5h');\n      });\n      test('Ps = 6 - Origin Mode (DECOM), VT100', async () => {\n        await ctx.proxy.write('\\x1b[?6h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).originMode, true);\n        await pollFor(ctx.page, () => getCursor(), { col: 0, row: 0 });\n        await ctx.proxy.write('\\x1b[2;3r');\n        await ctx.proxy.write('\\x1b[1;1HX');\n        await pollFor(ctx.page, () => getLinesAsArray(3), ['', 'X', '']);\n      });\n      test('Ps = 7 - Auto-Wrap Mode (DECAWM), VT100', async () => {\n        await ctx.proxy.resize(5, 2);\n        await ctx.proxy.write('\\x1b[?7h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).wraparoundMode, true);\n        await ctx.proxy.write('12345X');\n        await pollFor(ctx.page, () => getLinesAsArray(2), ['12345', 'X']);\n        await ctx.proxy.reset();\n        await ctx.proxy.write('\\x1b[?7l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).wraparoundMode, false);\n        await ctx.proxy.write('12345X');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['1234X']);\n        await ctx.proxy.resize(80, 24);\n      });\n      test('Ps = 8 - Auto-Repeat Keys (DECARM), VT100', async () => {\n        await assertNoModeChange('\\x1b[?8h');\n      });\n      test('Ps = 9 - Send Mouse X & Y on button press', async () => {\n        const selectionBefore = await dragSelection();\n        ok(selectionBefore > 0);\n        await ctx.proxy.write('\\x1b[?9h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'x10');\n        const selectionAfter = await dragSelection();\n        ok(selectionAfter === 0);\n      });\n      test('Ps = 1 0 - Show toolbar (rxvt)', async () => {\n        await assertNoModeChange('\\x1b[?10h');\n      });\n      test('Ps = 1 2 - Start blinking cursor (AT&T 610)', async () => {\n        const previousQuirks = await ctx.proxy.getOption('quirks');\n        const previousCursorBlink = await ctx.proxy.getOption('cursorBlink');\n        await ctx.proxy.setOption('quirks', { ...(previousQuirks ?? {}), allowSetCursorBlink: true });\n        await ctx.proxy.write('\\x1b[?12h');\n        await pollFor(ctx.page, async () => await ctx.proxy.getOption('cursorBlink'), true);\n        await ctx.proxy.setOption('quirks', previousQuirks);\n        await ctx.proxy.setOption('cursorBlink', previousCursorBlink);\n      });\n      test('Ps = 1 3 - Start blinking cursor (set only via resource or menu)', async () => {\n        await assertNoModeChange('\\x1b[?13h');\n      });\n      test('Ps = 1 4 - Enable XOR of blinking cursor control sequence and menu', async () => {\n        await assertNoModeChange('\\x1b[?14h');\n      });\n      test('Ps = 1 8 - Print Form Feed (DECPFF), VT220', async () => {\n        await assertNoModeChange('\\x1b[?18h');\n      });\n      test('Ps = 1 9 - Set print extent to full screen (DECPEX), VT220', async () => {\n        await assertNoModeChange('\\x1b[?19h');\n      });\n      test('Ps = 2 5 - Show cursor (DECTCEM), VT220', async () => {\n        await ctx.proxy.write('\\x1b[?25l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, false);\n        await ctx.proxy.write('\\x1b[?25h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, true);\n      });\n      test('Ps = 3 0 - Show scrollbar (rxvt)', async () => {\n        await assertNoModeChange('\\x1b[?30h');\n      });\n      test('Ps = 3 5 - Enable font-shifting functions (rxvt)', async () => {\n        await assertNoModeChange('\\x1b[?35h');\n      });\n      test('Ps = 3 8 - Enter Tektronix mode (DECTEK), VT240, xterm', async () => {\n        await assertNoModeChange('\\x1b[?38h');\n      });\n      test('Ps = 4 0 - Allow 80 ⇒  132 mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?40h');\n      });\n      test('Ps = 4 1 - more(1) fix (see curses resource)', async () => {\n        await assertNoModeChange('\\x1b[?41h');\n      });\n      test('Ps = 4 2 - Enable National Replacement Character sets (DECNRCM), VT220', async () => {\n        await assertNoModeChange('\\x1b[?42h');\n      });\n      test('Ps = 4 3 - Enable Graphic Expanded Print Mode (DECGEPM), VT340', async () => {\n        await assertNoModeChange('\\x1b[?43h');\n      });\n      test('Ps = 4 4 - Turn on margin bell, xterm', async () => {\n        await assertNoModeChange('\\x1b[?44h');\n      });\n      test('Ps = 4 4 - Enable Graphic Print Color Mode (DECGPCM), VT340', async () => {\n        await assertNoModeChange('\\x1b[?44h');\n      });\n      test('Ps = 4 5 - Reverse-wraparound mode (XTREVWRAP), xterm', async () => {\n        await ctx.proxy.write('\\x1b[?45h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).reverseWraparoundMode, true);\n        await ctx.proxy.resize(5, 2);\n        await ctx.proxy.write('\\x1b[?7h');\n        await ctx.proxy.write('12345X');\n        await ctx.proxy.write('\\r\\bY');\n        await pollFor(ctx.page, () => getLinesAsArray(2), ['1234Y', 'X']);\n        await ctx.proxy.resize(80, 24);\n      });\n      test.skip('Ps = 4 5 - Enable Graphic Print Color Syntax (DECGPCS), VT340', async () => {\n      });\n      test('Ps = 4 6 - Start logging (XTLOGGING), xterm', async () => {\n        await assertNoModeChange('\\x1b[?46h');\n      });\n      test('Ps = 4 6 - Graphic Print Background Mode, VT340', async () => {\n        await assertNoModeChange('\\x1b[?46h');\n      });\n      test('Ps = 4 7 - Use Alternate Screen Buffer, xterm', async () => {\n        await ctx.proxy.write('main');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);\n        await ctx.proxy.write('\\x1b[?47h');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['']);\n        await ctx.proxy.write('\\x1b[Halt');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['alt']);\n        await ctx.proxy.write('\\x1b[?47l');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);\n      });\n      test.skip('Ps = 4 7 - Enable Graphic Rotated Print Mode (DECGRPM), VT340', async () => {\n      });\n      test('Ps = 6 6 - Application keypad mode (DECNKM), VT320', async () => {\n        await ctx.proxy.write('\\x1b[?66h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, true);\n      });\n      test('Ps = 6 7 - Backarrow key sends backspace (DECBKM), VT340, VT420', async () => {\n        await assertNoModeChange('\\x1b[?67h');\n      });\n      test('Ps = 6 9 - Enable left and right margin mode (DECLRMM), VT420 and up', async () => {\n        await assertNoModeChange('\\x1b[?69h');\n      });\n      // test.skip('Ps = 8 0 - Enable Sixel Display Mode (DECSDM), VT330, VT340, VT382', async () => {\n      // });\n      test('Ps = 9 5 - Do not clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up', async () => {\n        await assertNoModeChange('\\x1b[?95h');\n      });\n      test('Ps = 1 0 0 0 - Send Mouse X & Y on button press and release', async () => {\n        const selectionBefore = await dragSelection();\n        ok(selectionBefore > 0);\n        await ctx.proxy.write('\\x1b[?1000h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'vt200');\n        const selectionAfter = await dragSelection();\n        ok(selectionAfter === 0);\n      });\n      test('Ps = 1 0 0 1 - Use Hilite Mouse Tracking, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1001h');\n      });\n      test('Ps = 1 0 0 2 - Use Cell Motion Mouse Tracking, xterm', async () => {\n        const selectionBefore = await dragSelection();\n        ok(selectionBefore > 0);\n        await ctx.proxy.write('\\x1b[?1002h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'drag');\n        const selectionAfter = await dragSelection();\n        ok(selectionAfter === 0);\n      });\n      test('Ps = 1 0 0 3 - Set Use All Motion (any event) Mouse Tracking', async () => {\n        const coords: { left: number, top: number, bottom: number, right: number } = await ctx.page.evaluate(`\n          (function() {\n            const rect = window.term.element.getBoundingClientRect();\n            return { left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right };\n          })();\n        `);\n        // Click and drag and ensure there is a selection\n        await ctx.page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);\n        await ctx.page.mouse.down();\n        await ctx.page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4);\n        ok((await ctx.proxy.getSelection()).length > 0, 'mouse events are off so there should be a selection');\n        await ctx.page.mouse.up();\n        // Clear selection\n        await ctx.page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);\n        await pollFor(ctx.page, async () => (await ctx.proxy.getSelection()).length, 0);\n        // Enable mouse events\n        await ctx.proxy.write('\\x1b[?1003h');\n        // Click and drag and ensure there is no selection\n        await ctx.page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);\n        await ctx.page.mouse.down();\n        await ctx.page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4);\n        // mouse events are on so there should be no selection\n        await pollFor(ctx.page, async () => (await ctx.proxy.getSelection()).length, 0);\n        await ctx.page.mouse.up();\n      });\n      test('Ps = 1 0 0 4 - Send FocusIn/FocusOut events, xterm', async () => {\n        await ctx.proxy.write('\\x1b[?1004h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).sendFocusMode, true);\n        await ctx.proxy.blur();\n        recordedData.length = 0;\n        await ctx.proxy.focus();\n        await ctx.proxy.blur();\n        await pollFor(ctx.page, () => recordedData, ['\\x1b[I', '\\x1b[O']);\n      });\n      test('Ps = 1 0 0 5 - Enable UTF-8 Mouse Mode, xterm', async () => {\n        await ctx.proxy.write('\\x1b[?1006h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n        await ctx.proxy.write('\\x1b[?1005h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n      });\n      test('Ps = 1 0 0 6 - Enable SGR Mouse Mode, xterm', async () => {\n        await ctx.proxy.write('\\x1b[?1006h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n      });\n      test('Ps = 1 0 0 7 - Enable Alternate Scroll Mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1007h');\n      });\n      test('Ps = 1 0 1 0 - Scroll to bottom on tty output (rxvt)', async () => {\n        await assertNoModeChange('\\x1b[?1010h');\n      });\n      test('Ps = 1 0 1 1 - Scroll to bottom on key press (rxvt)', async () => {\n        await assertNoModeChange('\\x1b[?1011h');\n      });\n      test('Ps = 1 0 1 5 - Enable urxvt Mouse Mode', async () => {\n        await ctx.proxy.write('\\x1b[?1006h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n        await ctx.proxy.write('\\x1b[?1015h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n      });\n      test('Ps = 1 0 1 6 - Enable SGR Mouse PixelMode, xterm', async () => {\n        await ctx.proxy.write('\\x1b[?1016h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR_PIXELS');\n      });\n      test('Ps = 1 0 3 4 - Interpret \"meta\" key, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1034h');\n      });\n      test('Ps = 1 0 3 5 - Enable special modifiers for Alt and NumLock keys, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1035h');\n      });\n      test('Ps = 1 0 3 6 - Send ESC   when Meta modifies a key, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1036h');\n      });\n      test('Ps = 1 0 3 7 - Send DEL from the editing-keypad Delete key, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1037h');\n      });\n      test('Ps = 1 0 3 9 - Send ESC  when Alt modifies a key, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1039h');\n      });\n      test('Ps = 1 0 4 0 - Keep selection even if not highlighted, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1040h');\n      });\n      test('Ps = 1 0 4 1 - Use the CLIPBOARD selection, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1041h');\n      });\n      test('Ps = 1 0 4 2 - Enable Urgency window manager hint when Control-G is received, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1042h');\n      });\n      test('Ps = 1 0 4 3 - Enable raising of the window when Control-G is received, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1043h');\n      });\n      test('Ps = 1 0 4 4 - Reuse the most recent data copied to CLIPBOARD, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1044h');\n      });\n      test('Ps = 1 0 4 5 - XTREVWRAP2: Extended Reverse-wraparound mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1045h');\n      });\n      test('Ps = 1 0 4 6 - Enable switching to/from Alternate Screen Buffer, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1046h');\n      });\n      test('Ps = 1 0 4 7 - Use Alternate Screen Buffer, xterm', async () => {\n        await ctx.proxy.write('main');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);\n        await ctx.proxy.write('\\x1b[?1047h');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['']);\n        await ctx.proxy.write('\\x1b[Halt');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['alt']);\n        await ctx.proxy.write('\\x1b[?1047l');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);\n      });\n      test('Ps = 1 0 4 8 - Save cursor as in DECSC, xterm', async () => {\n        await ctx.proxy.write('\\x1b[4;5H');\n        await ctx.proxy.write('\\x1b[?1048h');\n        await ctx.proxy.write('\\x1b[1;1H');\n        await ctx.proxy.write('\\x1b[?1048l');\n        await pollFor(ctx.page, () => getCursor(), { col: 4, row: 3 });\n      });\n      test('Ps = 1 0 4 9 - Save cursor as in DECSC, xterm', async () => {\n        await ctx.proxy.write('main');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);\n        await ctx.proxy.write('\\x1b[4;6H');\n        await ctx.proxy.write('\\x1b[?1049h');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');\n        await ctx.proxy.write('\\x1b[Halt');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['alt']);\n        await ctx.proxy.write('\\x1b[?1049l');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);\n        await pollFor(ctx.page, () => getCursor(), { col: 5, row: 3 });\n      });\n      test('Ps = 1 0 5 0 - Set terminfo/termcap function-key mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1050h');\n      });\n      test('Ps = 1 0 5 1 - Set Sun function-key mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1051h');\n      });\n      test('Ps = 1 0 5 2 - Set HP function-key mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1052h');\n      });\n      test('Ps = 1 0 5 3 - Set SCO function-key mode, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1053h');\n      });\n      test('Ps = 1 0 6 0 - Set legacy keyboard emulation, i.e, X11R6, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1060h');\n      });\n      test('Ps = 1 0 6 1 - Set VT220 keyboard emulation, xterm', async () => {\n        await assertNoModeChange('\\x1b[?1061h');\n      });\n      test('Ps = 2 0 0 1 - Enable readline mouse button-1, xterm', async () => {\n        await assertNoModeChange('\\x1b[?2001h');\n      });\n      test('Ps = 2 0 0 2 - Enable readline mouse button-2, xterm', async () => {\n        await assertNoModeChange('\\x1b[?2002h');\n      });\n      test('Ps = 2 0 0 3 - Enable readline mouse button-3, xterm', async () => {\n        await assertNoModeChange('\\x1b[?2003h');\n      });\n      test('Pm = 2 0 0 4, Set bracketed paste mode', async () => {\n        if (ctx.browser.browserType().name() !== 'chromium') {\n          test.skip();\n          return;\n        }\n        await pollFor(ctx.page, () => simulatePaste('foo'), 'foo');\n        await ctx.proxy.write('\\x1b[?2004h');\n        await pollFor(ctx.page, () => simulatePaste('bar'), '\\x1b[200~bar\\x1b[201~');\n        await ctx.proxy.write('\\x1b[?2004l');\n        await pollFor(ctx.page, () => simulatePaste('baz'), 'baz');\n      });\n      test('Ps = 2 0 0 5 - Enable readline character-quoting, xterm', async () => {\n        await assertNoModeChange('\\x1b[?2005h');\n      });\n      test('Ps = 2 0 0 6 - Enable readline newline pasting, xterm', async () => {\n        await assertNoModeChange('\\x1b[?2006h');\n      });\n    });\n    test.skip('CSI Ps i - MC: Media Copy', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI ? Ps i - MC: Media Copy, DEC-specified', async () => {\n      // TODO: Implement\n    });\n    test('CSI Pm l - RM: Reset Mode', async () => {\n      await ctx.proxy.write('\\x1b[4h\\x1b[20h');\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).insertMode, true);\n      await pollFor(ctx.page, async () => await ctx.proxy.getOption('convertEol'), true);\n      await ctx.proxy.write('\\x1b[4l\\x1b[20l');\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).insertMode, false);\n      await pollFor(ctx.page, async () => await ctx.proxy.getOption('convertEol'), false);\n    });\n    test.describe('CSI ? Pm l - DECRST: DEC Private Mode Reset', async () => {\n      test('Ps = 1 - Normal Cursor Keys (DECCKM), VT100.', async () => {\n        await ctx.proxy.write('\\x1b[?1h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationCursorKeysMode, true);\n        await ctx.proxy.write('\\x1b[?1l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationCursorKeysMode, false);\n      });\n      test('Ps = 2 - Designate VT52 mode (DECANM), VT100.', async () => {\n        await assertNoModeChange('\\x1b[?2l');\n      });\n      test('Ps = 3 - 80 Column Mode (DECCOLM), VT100.', async () => {\n        const windowOptions = await ctx.proxy.getOption('windowOptions');\n        await ctx.proxy.setOption('windowOptions', { ...windowOptions, setWinLines: true });\n        await ctx.proxy.write('\\x1b[?3h');\n        await pollFor(ctx.page, async () => await ctx.proxy.cols, 132);\n        await ctx.proxy.write('\\x1b[?3l');\n        await pollFor(ctx.page, async () => await ctx.proxy.cols, 80);\n        await ctx.proxy.resize(80, 24);\n        await ctx.proxy.setOption('windowOptions', windowOptions);\n      });\n      test('Ps = 4 - Jump (Fast) Scroll (DECSCLM), VT100.', async () => {\n        await assertNoModeChange('\\x1b[?4l');\n      });\n      test('Ps = 5 - Normal Video (DECSCNM), VT100.', async () => {\n        await assertNoModeChange('\\x1b[?5l');\n      });\n      test('Ps = 6 - Normal Cursor Mode (DECOM), VT100.', async () => {\n        await ctx.proxy.write('\\x1b[?6h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).originMode, true);\n        await ctx.proxy.write('\\x1b[?6l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).originMode, false);\n      });\n      test('Ps = 7 - No Auto-Wrap Mode (DECAWM), VT100.', async () => {\n        await ctx.proxy.resize(5, 2);\n        await ctx.proxy.write('\\x1b[?7h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).wraparoundMode, true);\n        await ctx.proxy.write('\\x1b[?7l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).wraparoundMode, false);\n        await ctx.proxy.write('12345X');\n        await pollFor(ctx.page, () => getLinesAsArray(1), ['1234X']);\n        await ctx.proxy.write('\\x1b[?7h');\n        await ctx.proxy.reset();\n        await ctx.proxy.write('\\x1b[?7h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).wraparoundMode, true);\n        await ctx.proxy.write('12345X');\n        await pollFor(ctx.page, () => getLinesAsArray(2), ['12345', 'X']);\n        await ctx.proxy.resize(80, 24);\n      });\n      test('Ps = 8 - No Auto-Repeat Keys (DECARM), VT100.', async () => {\n        await assertNoModeChange('\\x1b[?8l');\n      });\n      test('Ps = 9 - Don\\'t send Mouse X & Y on button press, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?9h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'x10');\n        await ctx.proxy.write('\\x1b[?9l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none');\n      });\n      test('Ps = 1 0 - Hide toolbar (rxvt).', async () => {\n        await assertNoModeChange('\\x1b[?10l');\n      });\n      test('Ps = 1 2 - Stop blinking cursor (AT&T 610).', async () => {\n        const previousQuirks = await ctx.proxy.getOption('quirks');\n        const previousCursorBlink = await ctx.proxy.getOption('cursorBlink');\n        await ctx.proxy.setOption('quirks', { ...(previousQuirks ?? {}), allowSetCursorBlink: true });\n        await ctx.proxy.setOption('cursorBlink', true);\n        await ctx.proxy.write('\\x1b[?12l');\n        await pollFor(ctx.page, async () => await ctx.proxy.getOption('cursorBlink'), false);\n        await ctx.proxy.setOption('quirks', previousQuirks);\n        await ctx.proxy.setOption('cursorBlink', previousCursorBlink);\n      });\n      test('Ps = 1 3 - Disable blinking cursor (reset only via resource or menu).', async () => {\n        await assertNoModeChange('\\x1b[?13l');\n      });\n      test('Ps = 1 4 - Disable XOR of blinking cursor control sequence and menu.', async () => {\n        await assertNoModeChange('\\x1b[?14l');\n      });\n      test('Ps = 1 8 - Don\\'t Print Form Feed (DECPFF), VT220.', async () => {\n        await assertNoModeChange('\\x1b[?18l');\n      });\n      test('Ps = 1 9 - Limit print to scrolling region (DECPEX), VT220.', async () => {\n        await assertNoModeChange('\\x1b[?19l');\n      });\n      test('Ps = 2 5 - Hide cursor (DECTCEM), VT220.', async () => {\n        await ctx.proxy.write('\\x1b[?25h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, true);\n        await ctx.proxy.write('\\x1b[?25l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, false);\n      });\n      test('Ps = 3 0 - Don\\'t show scrollbar (rxvt).', async () => {\n        await assertNoModeChange('\\x1b[?30l');\n      });\n      test('Ps = 3 5 - Disable font-shifting functions (rxvt).', async () => {\n        await assertNoModeChange('\\x1b[?35l');\n      });\n      test('Ps = 4 0 - Disallow 80 ⇒  132 mode, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?40l');\n      });\n      test('Ps = 4 1 - No more(1) fix (see curses resource).', async () => {\n        await assertNoModeChange('\\x1b[?41l');\n      });\n      test('Ps = 4 2 - Disable National Replacement Character sets (DECNRCM), VT220.', async () => {\n        await assertNoModeChange('\\x1b[?42l');\n      });\n      test('Ps = 4 3 - Disable Graphic Expanded Print Mode (DECGEPM), VT340.', async () => {\n        await assertNoModeChange('\\x1b[?43l');\n      });\n      test('Ps = 4 4 - Turn off margin bell, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?44l');\n      });\n      test('Ps = 4 4 - Disable Graphic Print Color Mode (DECGPCM), VT340.', async () => {\n        await assertNoModeChange('\\x1b[?44l');\n      });\n      test('Ps = 4 5 - No Reverse-wraparound mode (XTREVWRAP), xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?45h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).reverseWraparoundMode, true);\n        await ctx.proxy.write('\\x1b[?45l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).reverseWraparoundMode, false);\n      });\n      test.skip('Ps = 4 5 - Disable Graphic Print Color Syntax (DECGPCS), VT340.', async () => {\n      });\n      test('Ps = 4 6 - Stop logging (XTLOGGING), xterm.  This is normally disabled by a compile-time option.', async () => {\n        await assertNoModeChange('\\x1b[?46l');\n      });\n      test('Ps = 4 7 - Use Normal Screen Buffer, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?47h');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');\n        await ctx.proxy.write('\\x1b[?47l');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');\n      });\n      test.skip('Ps = 4 7 - Disable Graphic Rotated Print Mode (DECGRPM), VT340.', async () => {\n      });\n      test('Ps = 6 6 - Numeric keypad mode (DECNKM), VT320.', async () => {\n        await ctx.proxy.write('\\x1b[?66h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, true);\n        await ctx.proxy.write('\\x1b[?66l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, false);\n      });\n      test('Ps = 6 7 - Backarrow key sends delete (DECBKM), VT340, VT420.  This sets the backarrowKey resource to \"false\".', async () => {\n        await assertNoModeChange('\\x1b[?67l');\n      });\n      test('Ps = 6 9 - Disable left and right margin mode (DECLRMM), VT420 and up.', async () => {\n        await assertNoModeChange('\\x1b[?69l');\n      });\n      // This is intentionally not implemented here are image support lives within an addon\n      // test.skip('Ps = 8 0 - Disable Sixel Display Mode (DECSDM), VT330, VT340, VT382.  Turns on \"Sixel Scrolling\".  See the section Sixel Graphics and mode 8 4 5 2 .', async () => {\n      // });\n      test('Ps = 9 5 - Clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up.', async () => {\n        await assertNoModeChange('\\x1b[?95l');\n      });\n      test('Ps = 1 0 0 0 - Don\\'t send Mouse X & Y on button press and release.  See the section Mouse Tracking.', async () => {\n        await ctx.proxy.write('\\x1b[?1000h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'vt200');\n        await ctx.proxy.write('\\x1b[?1000l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none');\n      });\n      test('Ps = 1 0 0 1 - Don\\'t use Hilite Mouse Tracking, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1001l');\n      });\n      test('Ps = 1 0 0 2 - Don\\'t use Cell Motion Mouse Tracking, xterm.  See the section Button-event tracking.', async () => {\n        await ctx.proxy.write('\\x1b[?1002h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'drag');\n        await ctx.proxy.write('\\x1b[?1002l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none');\n      });\n      test('Ps = 1 0 0 3 - Don\\'t use All Motion Mouse Tracking, xterm. See the section Any-event tracking.', async () => {\n        await ctx.proxy.write('\\x1b[?1003h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'any');\n        await ctx.proxy.write('\\x1b[?1003l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none');\n      });\n      test('Ps = 1 0 0 4 - Don\\'t send FocusIn/FocusOut events, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?1004h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).sendFocusMode, true);\n        await ctx.proxy.write('\\x1b[?1004l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).sendFocusMode, false);\n      });\n      test('Ps = 1 0 0 5 - Disable UTF-8 Mouse Mode, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?1006h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n        await ctx.proxy.write('\\x1b[?1005l');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n      });\n      test('Ps = 1 0 0 6 - Disable SGR Mouse Mode, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?1006h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n        await ctx.proxy.write('\\x1b[?1006l');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'DEFAULT');\n      });\n      test('Ps = 1 0 0 7 - Disable Alternate Scroll Mode, xterm.  This corresponds to the alternateScroll resource.', async () => {\n        await assertNoModeChange('\\x1b[?1007l');\n      });\n      test('Ps = 1 0 1 0 - Don\\'t scroll to bottom on tty output (rxvt).  This sets the scrollTtyOutput resource to \"false\".', async () => {\n        await assertNoModeChange('\\x1b[?1010l');\n      });\n      test('Ps = 1 0 1 1 - Don\\'t scroll to bottom on key press (rxvt). This sets the scrollKey resource to \"false\".', async () => {\n        await assertNoModeChange('\\x1b[?1011l');\n      });\n      test('Ps = 1 0 1 5 - Disable urxvt Mouse Mode.', async () => {\n        await ctx.proxy.write('\\x1b[?1006h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n        await ctx.proxy.write('\\x1b[?1015l');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');\n      });\n      test('Ps = 1 0 1 6 - Disable SGR Mouse Pixel-Mode, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?1016h');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR_PIXELS');\n        await ctx.proxy.write('\\x1b[?1016l');\n        await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'DEFAULT');\n      });\n      test('Ps = 1 0 3 4 - Don\\'t interpret \"meta\" key, xterm.  This disables the eightBitInput resource.', async () => {\n        await assertNoModeChange('\\x1b[?1034l');\n      });\n      test('Ps = 1 0 3 5 - Disable special modifiers for Alt and NumLock keys, xterm.  This disables the numLock resource.', async () => {\n        await assertNoModeChange('\\x1b[?1035l');\n      });\n      test('Ps = 1 0 3 6 - Don\\'t send ESC  when Meta modifies a key, xterm.  This disables the metaSendsEscape resource.', async () => {\n        await assertNoModeChange('\\x1b[?1036l');\n      });\n      test('Ps = 1 0 3 7 - Send VT220 Remove from the editing-keypad Delete key, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1037l');\n      });\n      test('Ps = 1 0 3 9 - Don\\'t send ESC when Alt modifies a key, xterm.  This disables the altSendsEscape resource.', async () => {\n        await assertNoModeChange('\\x1b[?1039l');\n      });\n      test('Ps = 1 0 4 0 - Do not keep selection when not highlighted, xterm.  This disables the keepSelection resource.', async () => {\n        await assertNoModeChange('\\x1b[?1040l');\n      });\n      test('Ps = 1 0 4 1 - Use the PRIMARY selection, xterm.  This disables the selectToClipboard resource.', async () => {\n        await assertNoModeChange('\\x1b[?1041l');\n      });\n      test('Ps = 1 0 4 2 - Disable Urgency window manager hint when Control-G is received, xterm.  This disables the bellIsUrgent resource.', async () => {\n        await assertNoModeChange('\\x1b[?1042l');\n      });\n      test('Ps = 1 0 4 3 - Disable raising of the window when Control- G is received, xterm.  This disables the popOnBell resource.', async () => {\n        await assertNoModeChange('\\x1b[?1043l');\n      });\n      test('Ps = 1 0 4 5 - No Extended Reverse-wraparound mode (XTREVWRAP2), xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1045l');\n      });\n      test('Ps = 1 0 4 6 - Disable switching to/from Alternate Screen Buffer, xterm.  This works for terminfo-based systems, updating the titeInhibit resource.  If currently using the Alternate Screen Buffer, xterm switches to the Normal Screen Buffer.', async () => {\n        await assertNoModeChange('\\x1b[?1046l');\n      });\n      test('Ps = 1 0 4 7 - Use Normal Screen Buffer, xterm.  Clear the screen first if in the Alternate Screen Buffer.  This may be disabled by the titeInhibit resource.', async () => {\n        await ctx.proxy.write('\\x1b[?1047h');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');\n        await ctx.proxy.write('\\x1b[?1047l');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');\n      });\n      test('Ps = 1 0 4 8 - Restore cursor as in DECRC, xterm.  This may be disabled by the titeInhibit resource.', async () => {\n        await ctx.proxy.write('\\x1b[4;6H');\n        await ctx.proxy.write('\\x1b[?1048h');\n        await ctx.proxy.write('\\x1b[1;1H');\n        await ctx.proxy.write('\\x1b[?1048l');\n        await pollFor(ctx.page, () => getCursor(), { col: 5, row: 3 });\n      });\n      test('Ps = 1 0 4 9 - Use Normal Screen Buffer and restore cursor as in DECRC, xterm.  This may be disabled by the titeInhibit resource.  This combines the effects of the 1 0 4 7  and 1 0 4 8  modes.  Use this with terminfo-based applications rather than the 4 7  mode.', async () => {\n        await ctx.proxy.write('\\x1b[3;4H');\n        await ctx.proxy.write('\\x1b[?1048h');\n        await ctx.proxy.write('\\x1b[?47h');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');\n        await ctx.proxy.write('\\x1b[1;1H');\n        await ctx.proxy.write('\\x1b[?1049l');\n        await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');\n        await pollFor(ctx.page, () => getCursor(), { col: 3, row: 2 });\n      });\n      test('Ps = 1 0 5 0 - Reset terminfo/termcap function-key mode, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1050l');\n      });\n      test('Ps = 1 0 5 1 - Reset Sun function-key mode, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1051l');\n      });\n      test('Ps = 1 0 5 2 - Reset HP function-key mode, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1052l');\n      });\n      test('Ps = 1 0 5 3 - Reset SCO function-key mode, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1053l');\n      });\n      test('Ps = 1 0 6 0 - Reset legacy keyboard emulation, i.e, X11R6, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1060l');\n      });\n      test('Ps = 1 0 6 1 - Reset keyboard emulation to Sun/PC style, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?1061l');\n      });\n      test('Ps = 2 0 0 1 - Disable readline mouse button-1, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?2001l');\n      });\n      test('Ps = 2 0 0 2 - Disable readline mouse button-2, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?2002l');\n      });\n      test('Ps = 2 0 0 3 - Disable readline mouse button-3, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?2003l');\n      });\n      test('Ps = 2 0 0 4 - Reset bracketed paste mode, xterm.', async () => {\n        await ctx.proxy.write('\\x1b[?2004h');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).bracketedPasteMode, true);\n        await ctx.proxy.write('\\x1b[?2004l');\n        await pollFor(ctx.page, async () => (await ctx.proxy.modes).bracketedPasteMode, false);\n      });\n      test('Ps = 2 0 0 5 - Disable readline character-quoting, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?2005l');\n      });\n      test('Ps = 2 0 0 6 - Disable readline newline pasting, xterm.', async () => {\n        await assertNoModeChange('\\x1b[?2006l');\n      });\n    });\n    test.describe('CSI Pm m - SGR: Character Attributes', () => {\n      test('Ps = 0 - Normal (default), VT100', async () => {\n        await ctx.proxy.write('\\x1b[1;3;4;5;7;8;9m#\\x1b[0m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isBold());\n        ok(await cell0!.isItalic());\n        ok(await cell0!.isUnderline());\n        ok(await cell0!.isBlink());\n        ok(await cell0!.isInverse());\n        ok(await cell0!.isInvisible());\n        ok(await cell0!.isStrikethrough());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isAttributeDefault(), true);\n      });\n      test('Ps = 1 - Bold, VT100', async () => {\n        await ctx.proxy.write('\\x1b[1m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isBold());\n      });\n      test('Ps = 2 - Faint, decreased intensity, ECMA-48 2nd', async () => {\n        await ctx.proxy.write('\\x1b[2m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isDim());\n      });\n      test('Ps = 3 - Italicized, ECMA-48 2nd', async () => {\n        await ctx.proxy.write('\\x1b[3m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isItalic());\n      });\n      test('Ps = 4 - Underlined, VT100', async () => {\n        await ctx.proxy.write('\\x1b[4m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isUnderline());\n      });\n      test('Ps = 5 - Blink, VT100', async () => {\n        await ctx.proxy.write('\\x1b[5m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isBlink());\n      });\n      test('Ps = 7 - Inverse, VT100', async () => {\n        await ctx.proxy.write('\\x1b[7m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isInverse());\n      });\n      test('Ps = 8 - Invisible, ECMA-48 2nd, VT300', async () => {\n        await ctx.proxy.write('\\x1b[8m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isInvisible());\n      });\n      test('Ps = 9 - Crossed-out characters, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[9m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isStrikethrough());\n      });\n      test('Ps = 21 - Doubly-underlined, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[21m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell!.isUnderline());\n      });\n      test('Ps = 22 - Normal (neither bold nor faint), ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[1;2m#\\x1b[22m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isBold());\n        ok(await cell0!.isDim());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isBold(), 0);\n        deepStrictEqual(await cell1!.isDim(), 0);\n      });\n      test('Ps = 23 - Not italicized, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[3m#\\x1b[23m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isItalic());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isItalic(), 0);\n      });\n      test('Ps = 24 - Not underlined, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[4m#\\x1b[24m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isUnderline());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isUnderline(), 0);\n      });\n      test('Ps = 25 - Steady (not blinking), ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[5m#\\x1b[25m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isBlink());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isBlink(), 0);\n      });\n      test('Ps = 27 - Positive (not inverse), ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[7m#\\x1b[27m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isInverse());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isInverse(), 0);\n      });\n      test('Ps = 28 - Visible, ECMA-48 3rd, VT300', async () => {\n        await ctx.proxy.write('\\x1b[8m#\\x1b[28m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isInvisible());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isInvisible(), 0);\n      });\n      test('Ps = 29 - Not crossed-out, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[9m#\\x1b[29m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        ok(await cell0!.isStrikethrough());\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isStrikethrough(), 0);\n      });\n      test('Ps = 30 - Set foreground color to Black', async () => {\n        await ctx.proxy.write('\\x1b[30m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 0);\n      });\n      test('Ps = 31 - Set foreground color to Red', async () => {\n        await ctx.proxy.write('\\x1b[31m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 1);\n      });\n      test('Ps = 32 - Set foreground color to Green', async () => {\n        await ctx.proxy.write('\\x1b[32m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 2);\n      });\n      test('Ps = 33 - Set foreground color to Yellow', async () => {\n        await ctx.proxy.write('\\x1b[33m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 3);\n      });\n      test('Ps = 34 - Set foreground color to Blue', async () => {\n        await ctx.proxy.write('\\x1b[34m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 4);\n      });\n      test('Ps = 35 - Set foreground color to Magenta', async () => {\n        await ctx.proxy.write('\\x1b[35m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 5);\n      });\n      test('Ps = 36 - Set foreground color to Cyan', async () => {\n        await ctx.proxy.write('\\x1b[36m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 6);\n      });\n      test('Ps = 37 - Set foreground color to White', async () => {\n        await ctx.proxy.write('\\x1b[37m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 7);\n      });\n      test('Ps = 39 - Set foreground color to default, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[31m#\\x1b[39m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell0!.isFgPalette(), true);\n        deepStrictEqual(await cell0!.getFgColor(), 1);\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isFgDefault(), true);\n      });\n      test('Ps = 40 - Set background color to Black', async () => {\n        await ctx.proxy.write('\\x1b[40m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 0);\n      });\n      test('Ps = 41 - Set background color to Red', async () => {\n        await ctx.proxy.write('\\x1b[41m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 1);\n      });\n      test('Ps = 42 - Set background color to Green', async () => {\n        await ctx.proxy.write('\\x1b[42m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 2);\n      });\n      test('Ps = 43 - Set background color to Yellow', async () => {\n        await ctx.proxy.write('\\x1b[43m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 3);\n      });\n      test('Ps = 44 - Set background color to Blue', async () => {\n        await ctx.proxy.write('\\x1b[44m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 4);\n      });\n      test('Ps = 45 - Set background color to Magenta', async () => {\n        await ctx.proxy.write('\\x1b[45m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 5);\n      });\n      test('Ps = 46 - Set background color to Cyan', async () => {\n        await ctx.proxy.write('\\x1b[46m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 6);\n      });\n      test('Ps = 47 - Set background color to White', async () => {\n        await ctx.proxy.write('\\x1b[47m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 7);\n      });\n      test('Ps = 49 - Set background color to default, ECMA-48 3rd', async () => {\n        await ctx.proxy.write('\\x1b[41m#\\x1b[49m@');\n        const cell0 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell0!.isBgPalette(), true);\n        deepStrictEqual(await cell0!.getBgColor(), 1);\n        const cell1 = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1);\n        deepStrictEqual(await cell1!.isBgDefault(), true);\n      });\n      test('Ps = 90 - Set foreground color to bright Black', async () => {\n        await ctx.proxy.write('\\x1b[90m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 8);\n      });\n      test('Ps = 91 - Set foreground color to bright Red', async () => {\n        await ctx.proxy.write('\\x1b[91m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 9);\n      });\n      test('Ps = 92 - Set foreground color to bright Green', async () => {\n        await ctx.proxy.write('\\x1b[92m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 10);\n      });\n      test('Ps = 93 - Set foreground color to bright Yellow', async () => {\n        await ctx.proxy.write('\\x1b[93m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 11);\n      });\n      test('Ps = 94 - Set foreground color to bright Blue', async () => {\n        await ctx.proxy.write('\\x1b[94m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 12);\n      });\n      test('Ps = 95 - Set foreground color to bright Magenta', async () => {\n        await ctx.proxy.write('\\x1b[95m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 13);\n      });\n      test('Ps = 96 - Set foreground color to bright Cyan', async () => {\n        await ctx.proxy.write('\\x1b[96m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 14);\n      });\n      test('Ps = 97 - Set foreground color to bright White', async () => {\n        await ctx.proxy.write('\\x1b[97m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 15);\n      });\n      test('Ps = 100 - Set background color to bright Black', async () => {\n        await ctx.proxy.write('\\x1b[100m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 8);\n      });\n      test('Ps = 101 - Set background color to bright Red', async () => {\n        await ctx.proxy.write('\\x1b[101m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 9);\n      });\n      test('Ps = 102 - Set background color to bright Green', async () => {\n        await ctx.proxy.write('\\x1b[102m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 10);\n      });\n      test('Ps = 103 - Set background color to bright Yellow', async () => {\n        await ctx.proxy.write('\\x1b[103m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 11);\n      });\n      test('Ps = 104 - Set background color to bright Blue', async () => {\n        await ctx.proxy.write('\\x1b[104m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 12);\n      });\n      test('Ps = 105 - Set background color to bright Magenta', async () => {\n        await ctx.proxy.write('\\x1b[105m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 13);\n      });\n      test('Ps = 106 - Set background color to bright Cyan', async () => {\n        await ctx.proxy.write('\\x1b[106m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 14);\n      });\n      test('Ps = 107 - Set background color to bright White', async () => {\n        await ctx.proxy.write('\\x1b[107m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 15);\n      });\n      test('Ps = 38:2:Pi:Pr:Pg:Pb - Set foreground color using RGB values (colon separator)', async () => {\n        await ctx.proxy.write('\\x1b[38:2::171:205:239m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgRGB(), true);\n        deepStrictEqual(await cell!.getFgColor(), 0xabcdef);\n      });\n      test('Ps = 38:5:Ps - Set foreground color to Ps using indexed color (colon separator)', async () => {\n        await ctx.proxy.write('\\x1b[38:5:123m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgPalette(), true);\n        deepStrictEqual(await cell!.getFgColor(), 123);\n      });\n      test('Ps = 48:2:Pi:Pr:Pg:Pb - Set background color using RGB values (colon separator)', async () => {\n        await ctx.proxy.write('\\x1b[48:2::18:52:86m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgRGB(), true);\n        deepStrictEqual(await cell!.getBgColor(), 0x123456);\n      });\n      test('Ps = 48:5:Ps - Set background color to Ps using indexed color (colon separator)', async () => {\n        await ctx.proxy.write('\\x1b[48:5:200m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgPalette(), true);\n        deepStrictEqual(await cell!.getBgColor(), 200);\n      });\n      test('Ps = 38;2;Pr;Pg;Pb - Set foreground color using RGB values (semicolon separator)', async () => {\n        await ctx.proxy.write('\\x1b[38;2;171;205;239m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isFgRGB(), true);\n        deepStrictEqual(await cell!.getFgColor(), 0xabcdef);\n      });\n      test('Ps = 48;2;Pr;Pg;Pb - Set background color using RGB values (semicolon separator)', async () => {\n        await ctx.proxy.write('\\x1b[48;2;18;52;86m#');\n        const cell = await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0);\n        deepStrictEqual(await cell!.isBgRGB(), true);\n        deepStrictEqual(await cell!.getBgColor(), 0x123456);\n      });\n    });\n    test.skip('CSI > Pp [; Pv] m - XTMODKEYS: Set/reset key modifier options, xterm', () => {\n      // TODO: Implement\n    });\n    test.skip('CSI ? Pp m - XTQMODKEYS: Query key modifier options, xterm', () => {\n      // TODO: Implement\n    });\n    test.describe('CSI Ps n - DSR: Device Status Report', () => {\n      test('Status Report - CSI 5 n', async () => {\n        await ctx.proxy.write('\\x1b[5n');\n        deepStrictEqual(recordedData, ['\\x1b[0n']);\n      });\n\n      test('Report Cursor Position (CPR) - CSI 6 n', async () => {\n        await ctx.proxy.write('\\n\\nfoo');\n        await pollFor(ctx.page, async () => [\n          await ctx.proxy.buffer.active.cursorY,\n          await ctx.proxy.buffer.active.cursorX\n        ], [2, 3]);\n        await ctx.proxy.write('\\x1b[6n');\n        deepStrictEqual(recordedData, ['\\x1b[3;4R']);\n      });\n\n      test('Report Cursor Position (DECXCPR) - CSI ? 6 n', async () => {\n        await ctx.proxy.write('\\n\\nfoo');\n        await pollFor(ctx.page, async () => [\n          await ctx.proxy.buffer.active.cursorY,\n          await ctx.proxy.buffer.active.cursorX\n        ], [2, 3]);\n        await ctx.proxy.write('\\x1b[?6n');\n        deepStrictEqual(recordedData, ['\\x1b[?3;4R']);\n      });\n    });\n    test.skip('CSI > Ps n - Disable key modifier options, xterm', () => {\n      // TODO: Implement\n    });\n    test.describe('CSI ? Ps n - DECDSR: Device Status Report (DEC-specific)', () => {\n      test('Color Scheme Query - CSI ? 996 n (dark theme)', async () => {\n        // Default theme has dark background (#000000) and light foreground (#ffffff)\n        await ctx.proxy.write('\\x1b[?996n');\n        deepStrictEqual(recordedData, ['\\x1b[?997;1n']);\n      });\n\n      test('Color Scheme Query - CSI ? 996 n (light theme)', async () => {\n        recordedData.length = 0;\n        await ctx.page.evaluate(`window.term.options.theme = { background: '#ffffff', foreground: '#000000' }`);\n        await ctx.proxy.write('\\x1b[?996n');\n        deepStrictEqual(recordedData, ['\\x1b[?997;2n']);\n        // Restore default theme\n        await ctx.page.evaluate(`window.term.options.theme = { background: '#000000', foreground: '#ffffff' }`);\n      });\n\n      test('Color Scheme Query disabled via vtExtensions.colorSchemeQuery', async () => {\n        recordedData.length = 0;\n        await ctx.page.evaluate(`window.term.options.vtExtensions = { colorSchemeQuery: false }`);\n        await ctx.proxy.write('\\x1b[?996n');\n        deepStrictEqual(recordedData, []);\n        // Re-enable\n        await ctx.page.evaluate(`window.term.options.vtExtensions = { colorSchemeQuery: true }`);\n      });\n    });\n    test.skip('CSI > Ps p - XTSMPOINTER: Set resource value pointerMode, xterm', () => {\n      // TODO: Implement\n    });\n    test('CSI ! p - DECSTR: Soft terminal reset, VT220 and up.', async () => {\n      const rows = await ctx.proxy.rows;\n      await ctx.proxy.write('\\x1b[4h\\x1b[?6h\\x1b[3;5r');\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).insertMode, true);\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).originMode, true);\n      await ctx.proxy.write('\\x1b[!p');\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).insertMode, false);\n      await pollFor(ctx.page, async () => (await ctx.proxy.modes).originMode, false);\n      await pollFor(ctx.page, () => ctx.page.evaluate(`({ top: window.term._core._bufferService.buffer.scrollTop, bottom: window.term._core._bufferService.buffer.scrollBottom })`), { top: 0, bottom: rows - 1 });\n    });\n    test.skip('CSI Pl ; Pc \" p - DECSCL: Set conformance level, VT220 and up.', () => {\n      // TODO: Implement\n    });\n    test('CSI Ps $ p - DECRQM: Request ANSI mode', async () => {\n      await ctx.proxy.write('\\x1b[4h');\n      recordedData.length = 0;\n      await ctx.proxy.write('\\x1b[4$p');\n      deepStrictEqual(recordedData, ['\\x1b[4;1$y']);\n      await ctx.proxy.write('\\x1b[4l');\n      recordedData.length = 0;\n      await ctx.proxy.write('\\x1b[4$p');\n      deepStrictEqual(recordedData, ['\\x1b[4;2$y']);\n      await ctx.proxy.write('\\x1b[20h');\n      recordedData.length = 0;\n      await ctx.proxy.write('\\x1b[20$p');\n      deepStrictEqual(recordedData, ['\\x1b[20;1$y']);\n      await ctx.proxy.write('\\x1b[20l');\n    });\n    test('CSI ? Ps $ p - Request DEC private mode (DECRQM).', async () => {\n      await ctx.proxy.write('\\x1b[?1h');\n      recordedData.length = 0;\n      await ctx.proxy.write('\\x1b[?1$p');\n      deepStrictEqual(recordedData, ['\\x1b[?1;1$y']);\n      await ctx.proxy.write('\\x1b[?1l');\n      recordedData.length = 0;\n      await ctx.proxy.write('\\x1b[?1$p');\n      deepStrictEqual(recordedData, ['\\x1b[?1;2$y']);\n    });\n    test.skip('CSI [Pm] # p - Push video attributes onto stack (XTPUSHSGR), xterm.  This is an alias for CSI # { , used to work around language limitations of C#.', async () => {\n      // TODO: Implement\n    });\n    test('CSI > Ps q - Report xterm name and version (XTVERSION).', async () => {\n      await ctx.proxy.write('\\x1b[>q');\n      ok(recordedData.length === 1);\n      ok(recordedData[0].startsWith('\\x1bP>|xterm.js('));\n      ok(recordedData[0].endsWith('\\x1b\\\\'));\n    });\n    test.skip('CSI Ps q - Load LEDs (DECLL), VT100.', async () => {\n      // TODO: Implement\n    });\n    test('CSI Ps SP q - Set cursor style (DECSCUSR), VT520.', async () => {\n      const getCursorMode = async () => ctx.proxy.core.evaluate(([core]) => {\n        const modes = core.coreService.decPrivateModes;\n        return { style: modes.cursorStyle, blink: modes.cursorBlink };\n      });\n      await ctx.proxy.write('\\x1b[1 q');\n      deepStrictEqual(await getCursorMode(), { style: 'block', blink: true });\n      await ctx.proxy.write('\\x1b[2 q');\n      deepStrictEqual(await getCursorMode(), { style: 'block', blink: false });\n      await ctx.proxy.write('\\x1b[3 q');\n      deepStrictEqual(await getCursorMode(), { style: 'underline', blink: true });\n      await ctx.proxy.write('\\x1b[4 q');\n      deepStrictEqual(await getCursorMode(), { style: 'underline', blink: false });\n      await ctx.proxy.write('\\x1b[5 q');\n      deepStrictEqual(await getCursorMode(), { style: 'bar', blink: true });\n      await ctx.proxy.write('\\x1b[6 q');\n      deepStrictEqual(await getCursorMode(), { style: 'bar', blink: false });\n      await ctx.proxy.write('\\x1b[0 q');\n      deepStrictEqual(await getCursorMode(), { style: undefined, blink: undefined });\n    });\n    test('CSI Ps \" q - Select character protection attribute (DECSCA), VT220.', async () => {\n      await ctx.proxy.write('\\x1b[1\"q');\n      await ctx.proxy.write('PROT');\n      await ctx.proxy.write('\\x1b[2\"q');\n      await ctx.proxy.write('open');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['PROTopen']);\n      await ctx.proxy.write('\\x1b[1;1H\\x1b[?2K');\n      await pollFor(ctx.page, () => getLinesAsArray(1), ['PROT']);\n    });\n    test.skip('CSI # q - Pop video attributes from stack (XTPOPSGR), xterm.', async () => {\n      // TODO: Implement\n    });\n    test('CSI Ps ; Ps r - Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM), VT100.', async () => {\n      const rows = await ctx.proxy.rows;\n      await ctx.proxy.write('\\x1b[2;4r');\n      await pollFor(ctx.page, () => ctx.page.evaluate(`({ top: window.term._core._bufferService.buffer.scrollTop, bottom: window.term._core._bufferService.buffer.scrollBottom })`), { top: 1, bottom: 3 });\n      await ctx.proxy.write('\\x1b[r');\n      await pollFor(ctx.page, () => ctx.page.evaluate(`({ top: window.term._core._bufferService.buffer.scrollTop, bottom: window.term._core._bufferService.buffer.scrollBottom })`), { top: 0, bottom: rows - 1 });\n    });\n    test.skip('CSI ? Pm r - Restore DEC Private Mode Values (XTRESTORE), xterm.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr ; Pm $ r - Change Attributes in Rectangular Area (DECCARA), VT400 and up.', async () => {\n      // TODO: Implement\n    });\n    test('CSI s - Save cursor, available only when DECLRMM is disabled (SCOSC, also ANSI.SYS).', async () => {\n      await ctx.proxy.write('\\x1b[3;4H');\n      await ctx.proxy.write('\\x1b[s');\n      await ctx.proxy.write('\\x1b[1;1H');\n      await ctx.proxy.write('\\x1b[u');\n      await pollFor(ctx.page, () => getCursor(), { col: 3, row: 2 });\n    });\n    test.skip('CSI Pl ; Pr s - Set left and right margins (DECSLRM), VT420 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI > Ps s - Set/reset shift-escape options (XTSHIFTESCAPE), xterm.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI ? Pm s - Save DEC Private Mode Values (XTSAVE), xterm.  Ps values are the same as for DECSET.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI > Pm t - This xterm control sets one or more features of the title modes (XTSMTITLE), xterm.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps SP t - Set warning-bell volume (DECSWBV), VT520.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr ; Pm $ t - Reverse Attributes in Rectangular Area (DECRARA), VT400 and up.', async () => {\n      // TODO: Implement\n    });\n    test('CSI u - Restore cursor (SCORC, also ANSI.SYS).', async () => {\n      await ctx.proxy.write('\\x1b[4;6H');\n      await ctx.proxy.write('\\x1b[s');\n      await ctx.proxy.write('\\x1b[1;1H');\n      await ctx.proxy.write('\\x1b[u');\n      await pollFor(ctx.page, () => getCursor(), { col: 5, row: 3 });\n    });\n    test.skip('CSI Ps SP u - Set margin-bell volume (DECSMBV), VT520.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr ; Pp ; Pt ; Pl ; Pp $ v - Copy Rectangular Area (DECCRA), VT400 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps $ w - Request presentation state report (DECRQPSR), VT320 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr \\' w - Enable Filter Rectangle (DECEFR), VT420 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps x - Request Terminal Parameters (DECREQTPARM).', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps * x - Select Attribute Change Extent (DECSACE), VT420 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pc ; Pt ; Pl ; Pb ; Pr $ x - Fill Rectangular Area (DECFRA), VT420 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps # y - Select checksum extension (XTCHECKSUM), xterm.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pi ; Pg ; Pt ; Pl ; Pb ; Pr * y - Request Checksum of Rectangular Area (DECRQCRA), VT420 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps ; Pu \\' z - Enable Locator Reporting (DECELR).', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr $ z - Erase Rectangular Area (DECERA), VT400 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pm \\' { - Select Locator Events (DECSLE).', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI [Pm] # { Push video attributes onto stack (XTPUSHSGR), xterm.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr $ { - Selective Erase Rectangular Area (DECSERA), VT400 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Pt ; Pl ; Pb ; Pr # | - Report selected graphic rendition (XTREPORTSGR), xterm.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps $ | - Select columns per page (DECSCPP), VT340.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps \\' | - Request Locator Position (DECRQLP).', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI Ps * | - Select number of lines per screen (DECSNLS), VT420 and up.', async () => {\n      // TODO: Implement\n    });\n    test.skip('CSI # } - Pop video attributes from stack (XTPOPSGR), xterm.', async () => {\n      // TODO: Implement\n    });\n    test('CSI Ps \\' } - Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.', async () => {\n      await ctx.proxy.resize(5, 5);\n      await ctx.proxy.write('12345'.repeat(6));\n      await ctx.proxy.write('\\x1b[3;3H');\n      await ctx.proxy.write('\\x1b[\\'}');\n      await pollFor(ctx.page, () => getLinesAsArray(6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);\n    });\n    test.skip('CSI Ps $ } - Select active status display (DECSASD), VT320 and up.', async () => {\n      // TODO: Implement\n    });\n    test('CSI Ps \\' ~ - Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.', async () => {\n      await ctx.proxy.resize(5, 5);\n      await ctx.proxy.write('12345'.repeat(6));\n      await ctx.proxy.write('\\x1b[3;3H');\n      await ctx.proxy.write('\\x1b[\\'~');\n      await pollFor(ctx.page, () => getLinesAsArray(6), ['12345', '1245', '1245', '1245', '1245', '1245']);\n    });\n    test.skip('CSI Ps $ ~ - Select status line type (DECSSDT), VT320 and up.', async () => {\n      // TODO: Implement\n    });\n    test.describe('CSI Ps ; Ps ; Ps t - Window Options', () => {\n      test('should be disabled by default', async () => {\n        await ctx.proxy.write('\\x1b[14t');\n        await ctx.proxy.write('\\x1b[16t');\n        await ctx.proxy.write('\\x1b[18t');\n        await ctx.proxy.write('\\x1b[20t');\n        await ctx.proxy.write('\\x1b[21t');\n        deepStrictEqual(recordedData, []);\n      });\n      test('14 - GetWinSizePixels', async () => {\n        await ctx.proxy.setOption('windowOptions', { getWinSizePixels: true });\n        await ctx.proxy.write('\\x1b[14t');\n        const d = await getDimensions();\n        deepStrictEqual(recordedData, [`\\x1b[4;${d.height};${d.width}t`]);\n      });\n      test('16 - GetCellSizePixels', async () => {\n        await ctx.proxy.setOption('windowOptions', { getCellSizePixels: true });\n        await ctx.proxy.write('\\x1b[16t');\n        const d = await getDimensions();\n        deepStrictEqual(recordedData, [`\\x1b[6;${d.cellHeight};${d.cellWidth}t`]);\n      });\n    });\n  });\n\n  test.describe('OSC', () => {\n    test.describe('OSC 4', () => {\n      test('query single color', async () => {\n        await ctx.proxy.write('\\x1b]4;0;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]4;0;rgb:2e2e/3434/3636\\x1b\\\\']);\n        await ctx.proxy.write('\\x1b]4;77;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]4;0;rgb:2e2e/3434/3636\\x1b\\\\', '\\x1b]4;77;rgb:5f5f/d7d7/5f5f\\x1b\\\\']);\n      });\n      test('query multiple colors', async () => {\n        await ctx.proxy.write('\\x1b]4;0;?;77;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]4;0;rgb:2e2e/3434/3636\\x1b\\\\', '\\x1b]4;77;rgb:5f5f/d7d7/5f5f\\x1b\\\\']);\n      });\n      test('set & query single color', async () => {\n        await ctx.proxy.write('\\x1b]4;0;?\\x07');\n        const restore = [...recordedData];\n        deepStrictEqual(recordedData, restore);\n        // set new color & query\n        await ctx.proxy.write('\\x1b]4;0;rgb:01/02/03\\x07\\x1b]4;0;?\\x07');\n        deepStrictEqual(recordedData, [restore[0], '\\x1b]4;0;rgb:0101/0202/0303\\x1b\\\\']);\n        // restore should set old color\n        await ctx.proxy.write(restore[0] + '\\x1b]4;0;?\\x07');\n        deepStrictEqual(recordedData, [restore[0], '\\x1b]4;0;rgb:0101/0202/0303\\x1b\\\\', restore[0]]);\n      });\n      test('query & set colors mixed', async () => {\n        await ctx.proxy.write('\\x1b]4;0;?;77;?\\x07');\n        const restore = [...recordedData];\n        recordedData.length = 0;\n        // mixed call - change 0, query 43, change 77\n        await ctx.proxy.write('\\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]4;43;rgb:0000/d7d7/afaf\\x1b\\\\']);\n        recordedData.length = 0;\n        // query new values for 0 + 77\n        await ctx.proxy.write('\\x1b]4;0;?;77;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]4;0;rgb:0101/0202/0303\\x1b\\\\', '\\x1b]4;77;rgb:aaaa/bbbb/cccc\\x1b\\\\']);\n        recordedData.length = 0;\n        // restore old values for 0 + 77\n        await ctx.proxy.write(restore[0] + restore[1] + '\\x1b]4;0;?;77;?\\x07');\n        deepStrictEqual(recordedData, restore);\n      });\n    });\n    test.describe('OSC 4 & 104', () => {\n      test('change & restore single color', async () => {\n        // test for some random color slots\n        for (const i of [0, 43, 77, 255]) {\n          await ctx.proxy.write(`\\x1b]4;${i};?\\x07`);\n          const restore = [...recordedData];\n          await ctx.proxy.write(`\\x1b]4;${i};rgb:01/02/03\\x07\\x1b]4;${i};?\\x07`);\n          deepStrictEqual(recordedData, [restore[0], `\\x1b]4;${i};rgb:0101/0202/0303\\x1b\\\\`]);\n          // restore slot color\n          await ctx.proxy.write(`\\x1b]104;${i}\\x07\\x1b]4;${i};?\\x07`);\n          deepStrictEqual(recordedData, [restore[0], `\\x1b]4;${i};rgb:0101/0202/0303\\x1b\\\\`, restore[0]]);\n          recordedData.length = 0;\n        }\n      });\n      test('restore multiple at once', async () => {\n        // change 3 random slots\n        await ctx.proxy.write(`\\x1b]4;0;?;43;?;77;?\\x07`);\n        const restore = [...recordedData];\n        recordedData.length = 0;\n        await ctx.proxy.write(`\\x1b]4;0;rgb:01/02/03;43;#aabbcc;77;#123456\\x07`);\n        // restore specific slots\n        await ctx.proxy.write(`\\x1b]104;0;43;77\\x07` + `\\x1b]4;0;?;43;?;77;?\\x07`);\n        deepStrictEqual(recordedData, restore);\n      });\n      test('restore full table', async () => {\n        // change 3 random slots\n        await ctx.proxy.write(`\\x1b]4;0;?;43;?;77;?\\x07`);\n        const restore = [...recordedData];\n        recordedData.length = 0;\n        await ctx.proxy.write(`\\x1b]4;0;rgb:01/02/03;43;#aabbcc;77;#123456\\x07`);\n        // restore all\n        await ctx.proxy.write(`\\x1b]104\\x07` + `\\x1b]4;0;?;43;?;77;?\\x07`);\n        deepStrictEqual(recordedData, restore);\n      });\n    });\n    test.describe('OSC 10 & 11 + 110 | 111 | 112', () => {\n      test('query FG color', async () => {\n        await ctx.proxy.write('\\x1b]10;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:ffff/ffff/ffff\\x1b\\\\']);\n      });\n      test('query BG color', async () => {\n        await ctx.proxy.write('\\x1b]11;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]11;rgb:0000/0000/0000\\x1b\\\\']);\n      });\n      test('query FG & BG color in one call', async () => {\n        await ctx.proxy.write('\\x1b]10;?;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:ffff/ffff/ffff\\x1b\\\\', '\\x1b]11;rgb:0000/0000/0000\\x1b\\\\']);\n      });\n      test('set & query FG', async () => {\n        await ctx.proxy.write('\\x1b]10;rgb:1/2/3\\x07\\x1b]10;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:1111/2222/3333\\x1b\\\\']);\n        await ctx.proxy.write('\\x1b]10;#ffffff\\x07\\x1b]10;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:1111/2222/3333\\x1b\\\\', '\\x1b]10;rgb:ffff/ffff/ffff\\x1b\\\\']);\n      });\n      test('set & query BG', async () => {\n        await ctx.proxy.write('\\x1b]11;rgb:1/2/3\\x07\\x1b]11;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]11;rgb:1111/2222/3333\\x1b\\\\']);\n        await ctx.proxy.write('\\x1b]11;#000000\\x07\\x1b]11;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]11;rgb:1111/2222/3333\\x1b\\\\', '\\x1b]11;rgb:0000/0000/0000\\x1b\\\\']);\n      });\n      test('set & query cursor color', async () => {\n        await ctx.proxy.write('\\x1b]12;rgb:1/2/3\\x07\\x1b]12;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]12;rgb:1111/2222/3333\\x1b\\\\']);\n        await ctx.proxy.write('\\x1b]12;#ffffff\\x07\\x1b]12;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]12;rgb:1111/2222/3333\\x1b\\\\', '\\x1b]12;rgb:ffff/ffff/ffff\\x1b\\\\']);\n      });\n      test('set & query FG & BG color in one call', async () => {\n        await ctx.proxy.write('\\x1b]10;#123456;rgb:aa/bb/cc\\x07\\x1b]10;?;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:1212/3434/5656\\x1b\\\\', '\\x1b]11;rgb:aaaa/bbbb/cccc\\x1b\\\\']);\n        await ctx.proxy.write('\\x1b]10;#ffffff;#000000\\x07');\n      });\n      test('OSC 110: restore FG color', async () => {\n        await ctx.proxy.write('\\x1b]10;rgb:1/2/3\\x07\\x1b]10;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:1111/2222/3333\\x1b\\\\']);\n        recordedData.length = 0;\n        // restore\n        await ctx.proxy.write('\\x1b]110\\x07\\x1b]10;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]10;rgb:ffff/ffff/ffff\\x1b\\\\']);\n      });\n      test('OSC 111: restore BG color', async () => {\n        await ctx.proxy.write('\\x1b]11;rgb:1/2/3\\x07\\x1b]11;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]11;rgb:1111/2222/3333\\x1b\\\\']);\n        recordedData.length = 0;\n        // restore\n        await ctx.proxy.write('\\x1b]111\\x07\\x1b]11;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]11;rgb:0000/0000/0000\\x1b\\\\']);\n      });\n      test('OSC 112: restore cursor color', async () => {\n        await ctx.proxy.write('\\x1b]12;rgb:1/2/3\\x07\\x1b]12;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]12;rgb:1111/2222/3333\\x1b\\\\']);\n        recordedData.length = 0;\n        // restore\n        await ctx.proxy.write('\\x1b]112\\x07\\x1b]12;?\\x07');\n        deepStrictEqual(recordedData, ['\\x1b]12;rgb:ffff/ffff/ffff\\x1b\\\\']);\n      });\n    });\n  });\n\n  test.describe('ESC', () => {\n    test.describe('DECRC: Save cursor, ESC 7', () => {\n      test('should save the absolute cursor position so resizing restores to the correct position', async () => {\n        await ctx.proxy.resize(10, 2);\n        await ctx.proxy.write('1\\n\\r2\\n\\r3\\n\\r4\\n\\r5');\n        await ctx.proxy.write('\\x1b7\\x1b[?47h');\n        await ctx.proxy.resize(10, 4);\n        await ctx.proxy.write('\\x1b[?47l\\x1b8');\n        await pollFor(ctx.page, () => getCursor(), { col: 1, row: 3 });\n      });\n    });\n  });\n});\n\n\nasync function getModeSnapshot(): Promise<any> {\n  return {\n    modes: await ctx.proxy.modes,\n    cursorBlink: await ctx.proxy.getOption('cursorBlink'),\n    cols: await ctx.proxy.cols,\n    rows: await ctx.proxy.rows,\n    bufferType: await ctx.proxy.buffer.active.type,\n    mouseProtocol: await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeProtocol),\n    mouseEncoding: await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding)\n  };\n}\n\nasync function assertNoModeChange(sequence: string): Promise<void> {\n  const before = await getModeSnapshot();\n  await ctx.proxy.write(sequence);\n  deepStrictEqual(await getModeSnapshot(), before);\n}\n\n\nasync function getLinesAsArray(count: number, start: number = 0): Promise<string[]> {\n  let text = '';\n  for (let i = start; i < start + count; i++) {\n    text += `window.term.buffer.active.getLine(${i}).translateToString(true),`;\n  }\n  return await ctx.page.evaluate(`[${text}]`);\n}\n\nasync function simulatePaste(text: string): Promise<string> {\n  const id = Math.floor(Math.random() * 1000000);\n  await ctx.page.evaluate(`\n    (function() {\n      window.disposable_${id} = window.term.onData(e => window.result_${id} = e);\n      const clipboardData = new DataTransfer();\n      clipboardData.setData('text/plain', '${text}');\n      window.term.textarea.dispatchEvent(new ClipboardEvent('paste', { clipboardData }));\n    })();\n  `);\n  const result = await ctx.page.evaluate<string>(`window.result_${id}`);\n  await ctx.page.evaluate(`window.disposable_${id}.dispose()`);\n  return result;\n}\n\nasync function dragSelection(): Promise<number> {\n  await ctx.proxy.clearSelection();\n  const coords: { left: number, top: number, bottom: number, right: number } = await ctx.page.evaluate(`\n    (function() {\n      const rect = window.term.element.getBoundingClientRect();\n      return { left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right };\n    })();\n  `);\n  await ctx.page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);\n  await ctx.page.mouse.down();\n  await ctx.page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4);\n  await ctx.page.mouse.up();\n  return (await ctx.proxy.getSelection()).length;\n}\n\nasync function getCursor(): Promise<{ col: number, row: number }> {\n  return {\n    col: await ctx.proxy.buffer.active.cursorX,\n    row: await ctx.proxy.buffer.active.cursorY\n  };\n}\n\nasync function getDimensions(): Promise<any> {\n  const dim = await ctx.proxy.dimensions;\n  return {\n    cellWidth: dim!.css.cell.width.toFixed(0),\n    cellHeight: dim!.css.cell.height.toFixed(0),\n    width: dim!.css.canvas.width.toFixed(0),\n    height: dim!.css.canvas.height.toFixed(0)\n  };\n}\n"
  },
  {
    "path": "test/playwright/MouseTracking.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { test } from '@playwright/test';\nimport { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\n// adjusted to work inside devcontainer\n// see https://github.com/xtermjs/xterm.js/issues/2379\nconst width = 1280;\nconst height = 960;\n\n// adjust terminal row/col size so we can test\n// >80 up to 223 and >255\nconst fontSize = 6;\nconst cols = 260;\nconst rows = 50;\n\n// for some reason shift gets not caught by selection manager on macos\nconst noShift = process.platform === 'darwin' ? false : true;\n\n/**\n * Helper functions.\n */\nasync function resetMouseModes(): Promise<void> {\n  await ctx.proxy.write('\\x1b[?9l\\x1b[?1000l\\x1b[?1001l\\x1b[?1002l\\x1b[?1003l');\n  await ctx.proxy.write('\\x1b[?1005l\\x1b[?1006l\\x1b[?1015l');\n}\n\nasync function getReports(encoding: string): Promise<any[]> {\n  const reports: any = await ctx.page.evaluate(`window.calls`);\n  await ctx.page.evaluate(`window.calls = [];`);\n  return reports.map((report: number[]) => parseReport(encoding, report));\n}\n\n// translate cell positions into pixel offset\n// always adds +2 in each direction so we dont end up in the wrong cell\n// due to rounding issues\nasync function cellPos(col: number, row: number): Promise<number[]> {\n  const coords: any = await ctx.page.evaluate(`\n    (function() {\n      const rect = window.term.element.getBoundingClientRect();\n      const dim = window.term.dimensions;\n      return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right, width: dim.css.cell.width, height: dim.css.cell.height};\n    })();\n  `);\n  return [col * coords.width + coords.left + 2, row * coords.height + coords.top + 2];\n}\n\n/**\n * Patched playwright functions.\n * This is needed to:\n *  - translate cell positions into pixel positions\n *  - allow modifiers to be set\n *  - fake wheel events\n */\nasync function mouseMove(col: number, row: number): Promise<void> {\n  const [xPixels, yPixels] = await cellPos(col, row);\n  await ctx.page.mouse.move(xPixels, yPixels);\n}\nasync function mouseDown(button: 'left' | 'right' | 'middle' | undefined): Promise<void> {\n  await ctx.page.mouse.down({ button });\n}\nasync function mouseUp(button: 'left' | 'right' | 'middle' | undefined): Promise<void> {\n  await ctx.page.mouse.up({ button });\n}\n\n// button definitions\nconst buttons: { [key: string]: number } = {\n  '<none>': -1,\n  left: 0,\n  middle: 1,\n  right: 2,\n  released: 3,\n  wheelUp: 4,\n  wheelDown: 5,\n  wheelLeft: 6,\n  wheelRight: 7,\n  aux8: 8,\n  aux9: 9,\n  aux10: 10,\n  aux11: 11,\n  aux12: 12,\n  aux13: 13,\n  aux14: 14,\n  aux15: 15\n};\nconst reverseButtons: any = {};\nfor (const el in buttons) {\n  reverseButtons[buttons[el]] = el;\n}\n\n// extract button data from buttonCode\nfunction evalButtonCode(code: number): any {\n  if (code > 255) {\n    return { button: 'invalid', action: 'invalid', modifier: {} };\n  }\n  const modifier = { shift: !!(code & 4), meta: !!(code & 8), control: !!(code & 16) };\n  const move = code & 32;\n  let button = code & 3;\n  if (code & 128) {\n    button |= 8;\n  }\n  if (code & 64) {\n    button |= 4;\n  }\n  let actionS = 'press';\n  let buttonS = reverseButtons[button];\n  if (button === 3) {\n    buttonS = '<none>';\n    actionS = 'release';\n  }\n  if (move) {\n    actionS = 'move';\n  } else if (4 <= button && button <= 7) {\n    buttonS = 'wheel';\n    actionS = button === 4 ? 'up' : button === 5 ? 'down' : button === 6 ? 'left' : 'right';\n  }\n  return { button: buttonS, action: actionS, modifier };\n}\n\n// parse a single mouse report\nfunction parseReport(encoding: string, msg: number[]): { state: any, row: number, col: number } | string {\n  let sReport: string;\n  let buttonCode: number;\n  let row: number;\n  let col: number;\n  // unpack msg\n  const report = String.fromCharCode.apply(null, msg);\n  // skip non mouse reports\n  if (!report || report[0] !== '\\x1b') {\n    return report;\n  }\n  switch (encoding) {\n    case 'DEFAULT':\n      return {\n        state: evalButtonCode(report.charCodeAt(3) - 32),\n        col: report.charCodeAt(4) - 32,\n        row: report.charCodeAt(5) - 32\n      };\n    case 'SGR':\n      sReport = report.slice(3, -1);\n      [buttonCode, col, row] = sReport.split(';').map(el => parseInt(el));\n      const state = evalButtonCode(buttonCode);\n      if (report[report.length - 1] === 'm') {\n        state.action = 'release';\n      }\n      return { state, row, col };\n    default:\n      return {\n        state: evalButtonCode(report.charCodeAt(3) - 32),\n        col: report.charCodeAt(4) - 32,\n        row: report.charCodeAt(5) - 32\n      };\n  }\n}\n\ntest.describe('Mouse Tracking Tests', () => {\n  test.beforeAll(async () => {\n    await ctx.page.setViewportSize({ width, height });\n    // patch terminal to get the onData calls\n    // we encode the msg here to an array of codes to not lose bytes\n    // (transmission strips non utf8 bytes)\n    // also resize so we can properly test the edge cases\n    await ctx.page.evaluate(`\n      window.term.onData(e => window.calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));\n      window.term.onBinary(e => window.calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));\n    `);\n  });\n\n  test.beforeEach(async () => {\n    await ctx.page.evaluate(`window.calls = [];`);\n    await ctx.proxy.setOption('fontSize', fontSize);\n    await ctx.proxy.resize(cols, rows);\n  });\n\n  test.describe('DECSET 9 (X10)', async () => {\n    /**\n     * X10 protocol:\n     *  - only press events\n     *  - no wheel\n     *  - no move\n     *  - no modifiers\n     */\n    test('default encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'DEFAULT';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?9h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // mouseup should not report\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), []);\n\n      // mousemove should not report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), []);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // test at max rows/cols\n      // capped at 223 (1-based)\n      await mouseMove(223 - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 223, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // higher than 223 should not report at all\n      await mouseMove(257, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), []);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), []);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), []);\n\n      // modifiers\n      // CTRL\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n\n      // ALT\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Alt');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // SHIFT\n      // note: caught by selection manager\n      // bug? Why not caught by selection manger on macos?\n      // bug: no modifier reported\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Shift');  // defaults to ShiftLeft\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Shift');\n      if (noShift) {\n        await pollFor(ctx.page, () => getReports(encoding), []);\n      } else {\n        await pollFor(ctx.page, () => getReports(encoding), [\n          { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n        ]);\n      }\n\n      // all modifiers\n      // bug: Shift not working - reporting totally wrong coords and modifiers - selection manager again?\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n    });\n    test('SGR encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'SGR';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?9h\\x1b[?1006h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // mouseup should not report\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), []);\n\n      // mousemove should not report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), []);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // test at max rows/cols\n      await mouseMove(cols - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: cols, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), []);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), []);\n\n      // modifiers\n      // CTRL\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n\n      // ALT\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Alt');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // SHIFT\n      // note: caught by selection manager\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Shift');  // defaults to ShiftLeft\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Shift');\n      if (noShift) {\n        await pollFor(ctx.page, () => getReports(encoding), []);\n      } else {\n        await pollFor(ctx.page, () => getReports(encoding), [\n          { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n        ]);\n      }\n\n      // all modifiers\n      // bug: Shift not working - reporting totally wrong coords and modifiers - selection manager again?\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }]);\n    });\n  });\n  test.describe('DECSET 1000 (VT200 mouse)', () => {\n    /**\n     * VT200 protocol:\n     *  - press and release events\n     *  - wheel up/down\n     *  - no move\n     *  - all modifiers\n     */\n    test('default encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'DEFAULT';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?1000h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mouseup should report, encoding cannot report released button\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mousemove should not report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), []);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 51, row: 11, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // test at max rows/cols\n      // capped at 223 (1-based)\n      await mouseMove(223 - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 223, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 223, row: rows, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'up', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // modifiers\n      // CTRL\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: true, shift: false, meta: false } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } }\n      ]);\n\n      // ALT\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Alt');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } }\n      ]);\n\n      // SHIFT\n      // note: press/release caught by selection manager\n      // bug: modifier not reported for passed events\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await ctx.page.keyboard.down('Shift');  // defaults to ShiftLeft\n      // await mouseDown('left');\n      // await mouseMove(44, 24);\n      // await mouseUp('left');\n      // // await wheelDown();\n      // await ctx.page.keyboard.up('Shift');\n      // // if (noShift) {\n      // //  await pollFor(ctx.page, () => getReports(encoding), [\n      // //    // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      // //  ]);\n      // // } else {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // // }\n\n      // all modifiers\n      // bug: Shift not working - selection manager?\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: true, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } }\n      ]);\n    });\n    test('SGR encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'SGR';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?1000h\\x1b[?1006h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mouseup should report\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mousemove should not report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), []);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 51, row: 11, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // test at max rows/cols\n      await mouseMove(cols - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: cols, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: cols, row: rows, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'right', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'up', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // modifiers\n      // CTRL\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } }\n      ]);\n\n      // ALT\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Alt');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } }\n      ]);\n\n      // SHIFT\n      // note: press/release caught by selection manager\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await ctx.page.keyboard.down('Shift');  // defaults to ShiftLeft\n      // await mouseDown('left');\n      // await mouseMove(44, 24);\n      // await mouseUp('left');\n      // await wheelDown();\n      // await ctx.page.keyboard.up('Shift');\n      // if (noShift) {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // } else {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // }\n\n      // all modifiers\n      // bug: Shift not working - selection manager?\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } }\n      ]);\n    });\n  });\n  test.describe('DECSET 1002 (xterm with drag)', () => {\n    /**\n     *  - press and release events\n     *  - wheel up/down\n     *  - move only on press (drag)\n     *  - all modifiers\n     * Note: tmux runs this with SGR encoding.\n     */\n    test('default encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'DEFAULT';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?1002h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mouseup should report, encoding cannot report released button\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mousemove should not report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), []);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 51, row: 11, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // test at max rows/cols\n      // capped at 223 (1-based)\n      await mouseMove(223 - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 223, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 223, row: rows, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'up', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // modifiers\n      // CTRL\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: true, shift: false, meta: false } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } }\n      ]);\n\n      // ALT\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Alt');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } }\n      ]);\n\n      // SHIFT\n      // note: press/release/drag caught by selection manager\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await ctx.page.keyboard.down('Shift');  // defaults to ShiftLeft\n      // await mouseDown('left');\n      // await mouseMove(44, 24);\n      // await mouseUp('left');\n      // await wheelDown();\n      // await ctx.page.keyboard.up('Shift');\n      // if (noShift) {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // } else {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // }\n\n      // all modifiers\n      // bug: Shift not working\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: true, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } }\n      ]);\n    });\n    test('SGR encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'SGR';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?1002h\\x1b[?1006h');\n\n      // test at 0,0\n      // bug: release is fired immediately\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mouseup should report, encoding cannot report released button\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mousemove should not report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), []);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 51, row: 11, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // test at max rows/cols\n      await mouseMove(cols - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: cols, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: cols, row: rows, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'right', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'up', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // modifiers\n      // CTRL\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } }\n      ]);\n\n      // ALT\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Alt');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } }\n      ]);\n\n      // SHIFT\n      // note: press/release/drag caught by selection manager\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await ctx.page.keyboard.down('Shift');  // defaults to ShiftLeft\n      // await mouseDown('left');\n      // await mouseMove(44, 24);\n      // await mouseUp('left');\n      // await wheelDown();\n      // await ctx.page.keyboard.up('Shift');\n      // if (noShift) {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // } else {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // }\n\n      // all modifiers\n      // bug: this is totally broken with wrong coords and messed up modifiers\n      await mouseMove(43, 24);\n      await getReports(encoding); // clear reports\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } }\n      ]);\n    });\n  });\n  test.describe('DECSET 1003 (xterm any event)', () => {\n    /**\n     *  - all events (press, release, wheel, move)\n     *  - all modifiers\n     */\n    test('default encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'DEFAULT';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?1003h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mouseup should report, encoding cannot report released button\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mousemove should report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 51, row: 11, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // test at max rows/cols\n      // capped at 223 (1-based)\n      await mouseMove(223 - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 223, row: rows, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } },\n        { col: 223, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 223, row: rows, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'up', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // modifiers\n      // CTRL\n      await ctx.page.keyboard.down('Control');\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: true, shift: false, meta: false } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: true, shift: false, meta: false } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } }\n      ]);\n\n      // ALT\n      await ctx.page.keyboard.down('Alt');\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: true } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } }\n      ]);\n\n      // SHIFT\n      // note: press/release/drag caught by selection manager\n      // await ctx.page.keyboard.down('Shift');\n      // await mouseMove(43, 24);\n      // await mouseDown('left');\n      // await mouseMove(44, 24);\n      // await mouseUp('left');\n      // await wheelDown();\n      // await ctx.page.keyboard.up('Shift');\n      // if (noShift) {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // } else {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // }\n\n      // all modifiers\n      // bug: Shift not working\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: true, shift: false, meta: true } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: '<none>', modifier: { control: true, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } }\n      ]);\n    });\n    test('SGR encoding', async () => {\n      if (ctx.browser.browserType().name() === 'webkit') {\n        test.skip();\n        return;\n      }\n      const encoding = 'SGR';\n      await resetMouseModes();\n      await mouseMove(0, 0);\n      await ctx.proxy.write('\\x1b[?1003h\\x1b[?1006h');\n\n      // test at 0,0\n      await mouseDown('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mouseup should report, encoding cannot report released button\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 1, row: 1, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // mousemove should report\n      await mouseMove(50, 10);\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 51, row: 11, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 51, row: 11, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // test at max rows/cols\n      // bug: we are capped at col 95 currently\n      // fix: allow values up to 223, any bigger should drop to 0\n      await mouseMove(cols - 1, rows - 1);\n      await mouseDown('left');\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: cols, row: rows, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } },\n        { col: cols, row: rows, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: cols, row: rows, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // button press/move/release tests\n      // left button\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n      // middle button\n      // bug: default action not cancelled (adds data to getReports from clipboard under X11)\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await mouseDown('middle');\n      // await mouseMove(44, 24);\n      // await mouseUp('middle');\n      // await pollFor(ctx.page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);\n      // right button\n      // bug: default action not cancelled (popup shown)\n      await mouseMove(43, 24);\n      await mouseDown('right');\n      await mouseMove(44, 24);\n      await mouseUp('right');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: false } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'right', modifier: { control: false, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'right', modifier: { control: false, shift: false, meta: false } } }\n      ]);\n\n      // wheel\n      // await mouseMove(43, 24);\n      // await getReports(encoding); // clear reports\n      // await wheelUp();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'up', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n      // await wheelDown();\n      // await pollFor(ctx.page, () => getReports(encoding), [{ col: 44, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: false } } }]);\n\n      // modifiers\n      // CTRL\n      await ctx.page.keyboard.down('Control');\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: true, shift: false, meta: false } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } }\n      ]);\n\n      // ALT\n      await ctx.page.keyboard.down('Alt');\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Alt');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: false, meta: true } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } }\n      ]);\n\n      // SHIFT\n      // note: press/release/drag caught by selection manager\n      // await ctx.page.keyboard.down('Shift');\n      // await mouseMove(43, 24);\n      // await mouseDown('left');\n      // await mouseMove(44, 24);\n      // await mouseUp('left');\n      // await wheelDown();\n      // await ctx.page.keyboard.up('Shift');\n      // if (noShift) {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // } else {\n      //   await pollFor(ctx.page, () => getReports(encoding), [\n      //     { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: true, meta: false } } },\n      //     { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: true, meta: false } } }\n      //   ]);\n      // }\n\n      // all modifiers\n      // bug: Shift not working\n      await ctx.page.keyboard.down('Control');\n      await ctx.page.keyboard.down('Alt');\n      // await ctx.page.keyboard.down('Shift');\n      await mouseMove(43, 24);\n      await mouseDown('left');\n      await mouseMove(44, 24);\n      await mouseUp('left');\n      // await wheelDown();\n      await ctx.page.keyboard.up('Control');\n      await ctx.page.keyboard.up('Alt');\n      // await ctx.page.keyboard.up('Shift');\n      await pollFor(ctx.page, () => getReports(encoding), [\n        { col: 44, row: 25, state: { action: 'move', button: '<none>', modifier: { control: true, shift: false, meta: true } } },\n        { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } },\n        { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } }\n        // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } }\n      ]);\n    });\n  });\n  /**\n   * move tests with multiple buttons pressed:\n   * currently not possible due to a limitation of the playwright mouse interface\n   * (saves only the last one pressed)\n   */\n});\n"
  },
  {
    "path": "test/playwright/Parser.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { test } from '@playwright/test';\nimport { deepStrictEqual, ok, strictEqual } from 'assert';\nimport type { IDisposable } from '@xterm/xterm';\nimport { createTestContext, ITestContext, openTerminal, pollFor } from './TestUtils';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ndeclare global {\n  interface Window { // eslint-disable-line @typescript-eslint/naming-convention\n    customCsiHandlerParams?: (number | number[])[][];\n    customCsiHandlerCallStack?: string[];\n    customDcsHandlerCallStack?: [string, (number | number[])[], string][];\n    customEscHandlerCallStack?: string[];\n    customOscHandlerCallStack?: string[][];\n    customApcHandlerCallStack?: string[][];\n    disposable?: IDisposable;\n    disposables?: IDisposable[];\n  }\n}\n\ntest.describe('Parser Integration Tests', () => {\n  test.beforeEach(async () => await ctx.proxy.reset());\n  test.afterEach(async () => {\n    await ctx.page.evaluate(() => {\n      window.disposable?.dispose();\n      window.disposable = undefined;\n      window.disposables?.forEach(e => e.dispose());\n      window.disposables = undefined;\n    });\n  });\n\n  test.describe('registerCsiHandler', () => {\n    test('should call custom CSI handler with js array params', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customCsiHandlerParams = [];\n        window.disposable = term.parser.registerCsiHandler({final: 'm'}, (params) => {\n          window.customCsiHandlerParams!.push(params);\n          return false;\n        });\n      });\n      await ctx.proxy.write('\\x1b[38;5;123mparams\\x1b[38:2::50:100:150msubparams');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customCsiHandlerParams), [\n        [38, 5, 123],\n        [38, [2, -1, 50, 100, 150]]\n      ]);\n    });\n    test('async', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customCsiHandlerCallStack = [];\n        window.customCsiHandlerParams = [];\n        window.disposables = [\n          term.parser.registerCsiHandler({intermediates:'+', final: 'Z'}, params => {\n            window.customCsiHandlerCallStack!.push('A');\n            window.customCsiHandlerParams!.push(params);\n            return false;\n          }),\n          term.parser.registerCsiHandler({intermediates:'+', final: 'Z'}, params => {\n            return new Promise(res => setTimeout(res, 50)).then(() => {\n              window.customCsiHandlerCallStack!.push('B');\n              window.customCsiHandlerParams!.push(params);\n              return false;\n            });\n          }),\n          term.parser.registerCsiHandler({intermediates:'+', final: 'Z'}, params => {\n            window.customCsiHandlerCallStack!.push('C');\n            window.customCsiHandlerParams!.push(params);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b[1;2+Z');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customCsiHandlerCallStack), [\n        'C',\n        'B',\n        'A'\n      ]);\n      deepStrictEqual(await ctx.page.evaluate(() => window.customCsiHandlerParams), [\n        [1, 2],\n        [1, 2],\n        [1, 2]\n      ]);\n    });\n  });\n  test.describe('registerDcsHandler', () => {\n    test('should respects return value', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customDcsHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {\n            window.customDcsHandlerCallStack!.push(['A', params, data]);\n            return false;\n          }),\n          term.parser.registerDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {\n            window.customDcsHandlerCallStack!.push(['B', params, data]);\n            return true;\n          }),\n          term.parser.registerDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {\n            window.customDcsHandlerCallStack!.push(['C', params, data]);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1bP1;2+psome data\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customDcsHandlerCallStack), [\n        ['C', [1, 2], 'some data'],\n        ['B', [1, 2], 'some data']\n      ]);\n    });\n    test('async', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customDcsHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerDcsHandler({intermediates:'+', final: 'q'}, (data, params) => {\n            window.customDcsHandlerCallStack!.push(['A', params, data]);\n            return false;\n          }),\n          term.parser.registerDcsHandler({intermediates:'+', final: 'q'}, (data, params) => {\n            return new Promise(res => setTimeout(res, 50)).then(() => {\n              window.customDcsHandlerCallStack!.push(['B', params, data]);\n              return false;\n            });\n          }),\n          term.parser.registerDcsHandler({intermediates:'+', final: 'q'}, (data, params) => {\n            window.customDcsHandlerCallStack!.push(['C', params, data]);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1bP1;2+qsome data\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customDcsHandlerCallStack), [\n        ['C', [1, 2], 'some data'],\n        ['B', [1, 2], 'some data'],\n        ['A', [1, 2], 'some data']\n      ]);\n    });\n  });\n  test.describe('registerEscHandler', () => {\n    test('should respects return value', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customEscHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerEscHandler({intermediates:'(', final: 'B'}, () => {\n            window.customEscHandlerCallStack!.push('A');\n            return false;\n          }),\n          term.parser.registerEscHandler({intermediates:'(', final: 'B'}, () => {\n            window.customEscHandlerCallStack!.push('B');\n            return true;\n          }),\n          term.parser.registerEscHandler({intermediates:'(', final: 'B'}, () => {\n            window.customEscHandlerCallStack!.push('C');\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b(B');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customEscHandlerCallStack), ['C', 'B']);\n    });\n    test('async', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customEscHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerEscHandler({intermediates:'(', final: 'Z'}, () => {\n            window.customEscHandlerCallStack!.push('A');\n            return false;\n          }),\n          term.parser.registerEscHandler({intermediates:'(', final: 'Z'}, () => {\n            return new Promise(res => setTimeout(res, 50)).then(() => {\n              window.customEscHandlerCallStack!.push('B');\n              return false;\n            });\n          }),\n          term.parser.registerEscHandler({intermediates:'(', final: 'Z'}, () => {\n            window.customEscHandlerCallStack!.push('C');\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b(Z');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customEscHandlerCallStack), ['C', 'B', 'A']);\n    });\n  });\n  test.describe('registerOscHandler', () => {\n    test('should respects return value', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customOscHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerOscHandler(1234, data => {\n            window.customOscHandlerCallStack!.push(['A', data]);\n            return false;\n          }),\n          term.parser.registerOscHandler(1234, data => {\n            window.customOscHandlerCallStack!.push(['B', data]);\n            return true;\n          }),\n          term.parser.registerOscHandler(1234, data => {\n            window.customOscHandlerCallStack!.push(['C', data]);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b]1234;some data\\x07');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customOscHandlerCallStack), [\n        ['C', 'some data'],\n        ['B', 'some data']\n      ]);\n    });\n    test('async', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customOscHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerOscHandler(666, data => {\n            window.customOscHandlerCallStack!.push(['A', data]);\n            return false;\n          }),\n          term.parser.registerOscHandler(666, data => {\n            return new Promise(res => setTimeout(res, 50)).then(() => {\n              window.customOscHandlerCallStack!.push(['B', data]);\n              return false;\n            });\n          }),\n          term.parser.registerOscHandler(666, data => {\n            window.customOscHandlerCallStack!.push(['C', data]);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b]666;some data\\x07');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customOscHandlerCallStack), [\n        ['C', 'some data'],\n        ['B', 'some data'],\n        ['A', 'some data']\n      ]);\n    });\n  });\n  test.describe('registerApcHandler', () => {\n    // TODO: Remove when Kitty Graphics tests added (real-world usage replaces this)\n    test('should call custom APC handler with identifier', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customApcHandlerCallStack = [];\n        // APC uses first character as identifier (e.g., 0x41 = 'A')\n        window.disposable = term.parser.registerApcHandler(0x41, data => {\n          window.customApcHandlerCallStack!.push(['handler', data]);\n          return true;\n        });\n      });\n      // APC format: ESC _ <identifier> <data> ESC \\\n      await ctx.proxy.write('\\x1b_Asome data here\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customApcHandlerCallStack), [\n        ['handler', 'some data here']\n      ]);\n    });\n    // TODO: Remove when Kitty Graphics tests added\n    test('should handle short data', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customApcHandlerCallStack = [];\n        window.disposable = term.parser.registerApcHandler(0x42, data => {\n          window.customApcHandlerCallStack!.push(['handler', data]);\n          return true;\n        });\n      });\n      // Short APC with minimal data\n      await ctx.proxy.write('\\x1b_Bhi\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customApcHandlerCallStack), [\n        ['handler', 'hi']\n      ]);\n    });\n    test('should respect return value', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customApcHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerApcHandler(0x43, data => {\n            window.customApcHandlerCallStack!.push(['A', data]);\n            return false;\n          }),\n          term.parser.registerApcHandler(0x43, data => {\n            window.customApcHandlerCallStack!.push(['B', data]);\n            return true;\n          }),\n          term.parser.registerApcHandler(0x43, data => {\n            window.customApcHandlerCallStack!.push(['C', data]);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b_Csome data\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customApcHandlerCallStack), [\n        ['C', 'some data'],\n        ['B', 'some data']\n      ]);\n    });\n    test('async', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customApcHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerApcHandler(0x44, data => {\n            window.customApcHandlerCallStack!.push(['A', data]);\n            return false;\n          }),\n          term.parser.registerApcHandler(0x44, data => {\n            return new Promise(res => setTimeout(res, 50)).then(() => {\n              window.customApcHandlerCallStack!.push(['B', data]);\n              return false;\n            });\n          }),\n          term.parser.registerApcHandler(0x44, data => {\n            window.customApcHandlerCallStack!.push(['C', data]);\n            return false;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b_Dsome data\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customApcHandlerCallStack), [\n        ['C', 'some data'],\n        ['B', 'some data'],\n        ['A', 'some data']\n      ]);\n    });\n    // TODO: Remove when Kitty Graphics tests added\n    test('should handle different identifiers independently', async () => {\n      await ctx.proxy.evaluate(([term]) => {\n        window.customApcHandlerCallStack = [];\n        window.disposables = [\n          term.parser.registerApcHandler(0x46, data => { // 'F'\n            window.customApcHandlerCallStack!.push(['F', data]);\n            return true;\n          }),\n          term.parser.registerApcHandler(0x58, data => { // 'X'\n            window.customApcHandlerCallStack!.push(['X', data]);\n            return true;\n          })\n        ];\n      });\n      await ctx.proxy.write('\\x1b_Ffirst data\\x1b\\\\');\n      await ctx.proxy.write('\\x1b_Xsecond data\\x1b\\\\');\n      deepStrictEqual(await ctx.page.evaluate(() => window.customApcHandlerCallStack), [\n        ['F', 'first data'],\n        ['X', 'second data']\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/playwright/Renderer.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { test } from '@playwright/test';\nimport { ITestContext, createTestContext, openTerminal } from './TestUtils';\nimport { ISharedRendererTestContext, injectSharedRendererTestsStandalone, injectSharedRendererTests } from './SharedRendererTests';\n\nlet ctx: ITestContext;\nconst ctxWrapper: ISharedRendererTestContext = {\n  value: undefined,\n  skipDomExceptions: true\n} as any;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  ctxWrapper.value = ctx;\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\ntest.describe('DOM Renderer Integration Tests', () => {\n  // HACK: Skip on WebKit, not clear why it's failing\n  test.skip(({ browserName }) => browserName === 'webkit', 'Skipped on WebKit');\n\n  injectSharedRendererTests(ctxWrapper);\n  injectSharedRendererTestsStandalone(ctxWrapper, () => {});\n});\n"
  },
  {
    "path": "test/playwright/SharedRendererTests.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IImage32, decodePng } from '@lunapaint/png-codec';\nimport { LocatorScreenshotOptions, test } from '@playwright/test';\nimport { ITheme } from '@xterm/xterm';\nimport { ITestContext, openTerminal, pollFor, pollForApproximate } from './TestUtils';\n\nexport interface ISharedRendererTestContext {\n  value: ITestContext;\n  skipDomExceptions?: boolean;\n}\n\n\nexport function injectSharedRendererTests(ctx: ISharedRendererTestContext): void {\n  test.beforeEach(async () => {\n    await ctx.value.proxy.reset();\n    ctx.value.page.evaluate(`\n      window.term.options.minimumContrastRatio = 1;\n      window.term.options.allowTransparency = false;\n      window.term.options.theme = undefined;\n    `);\n    // Clear the cached screenshot before each test\n    frameDetails = undefined;\n  });\n\n  test.describe('colors', () => {\n    test('foreground 0-15', async () => {\n      const theme: ITheme = {\n        black: '#010203',\n        red: '#040506',\n        green: '#070809',\n        yellow: '#0a0b0c',\n        blue: '#0d0e0f',\n        magenta: '#101112',\n        cyan: '#131415',\n        white: '#161718'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.write(`\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■`);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n    });\n  });\n\n  test('foreground 0-7 drawBoldTextInBrightColors', async () => {\n    const theme: ITheme = {\n      brightBlack: '#010203',\n      brightRed: '#040506',\n      brightGreen: '#070809',\n      brightYellow: '#0a0b0c',\n      brightBlue: '#0d0e0f',\n      brightMagenta: '#101112',\n      brightCyan: '#131415',\n      brightWhite: '#161718'\n    };\n    await ctx.value.page.evaluate(`\n      window.term.options.theme = ${JSON.stringify(theme)};\n      window.term.options.drawBoldTextInBrightColors = true;\n    `);\n    await ctx.value.proxy.write(`\\x1b[1;30m■\\x1b[1;31m■\\x1b[1;32m■\\x1b[1;33m■\\x1b[1;34m■\\x1b[1;35m■\\x1b[1;36m■\\x1b[1;37m■`);\n    await pollForCellColorFresh(ctx.value, 1, 1, [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n  });\n\n  test('background 0-15', async () => {\n    const theme: ITheme = {\n      black: '#010203',\n      red: '#040506',\n      green: '#070809',\n      yellow: '#0a0b0c',\n      blue: '#0d0e0f',\n      magenta: '#101112',\n      cyan: '#131415',\n      white: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await ctx.value.proxy.write(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n  });\n\n  test('foreground 0-15 inverse', async () => {\n    const theme: ITheme = {\n      black: '#010203',\n      red: '#040506',\n      green: '#070809',\n      yellow: '#0a0b0c',\n      blue: '#0d0e0f',\n      magenta: '#101112',\n      cyan: '#131415',\n      white: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await ctx.value.proxy.write(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n  });\n\n  test('background 0-15 inverse', async () => {\n    const theme: ITheme = {\n      black: '#010203',\n      red: '#040506',\n      green: '#070809',\n      yellow: '#0a0b0c',\n      blue: '#0d0e0f',\n      magenta: '#101112',\n      cyan: '#131415',\n      white: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await ctx.value.proxy.write(`\\x1b[7;40m■\\x1b[7;41m■\\x1b[7;42m■\\x1b[7;43m■\\x1b[7;44m■\\x1b[7;45m■\\x1b[7;46m■\\x1b[7;47m■`);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n  });\n\n  test('foreground 0-15 invisible', async () => {\n    const theme: ITheme = {\n      black: '#010203',\n      red: '#040506',\n      green: '#070809',\n      yellow: '#0a0b0c',\n      blue: '#0d0e0f',\n      magenta: '#101112',\n      cyan: '#131415',\n      white: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await ctx.value.proxy.write(`\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0, 0, 0, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0, 0, 0, 255]);\n  });\n\n  test('background 0-15 invisible', async () => {\n    const theme: ITheme = {\n      black: '#010203',\n      red: '#040506',\n      green: '#070809',\n      yellow: '#0a0b0c',\n      blue: '#0d0e0f',\n      magenta: '#101112',\n      cyan: '#131415',\n      white: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(`\\x1b[8;40m■\\x1b[8;41m■\\x1b[8;42m■\\x1b[8;43m■\\x1b[8;44m■\\x1b[8;45m■\\x1b[8;46m■\\x1b[8;47m■`));\n    const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);\n    await pollFor(ctx.value.page, () => getBgColor(1, 1), [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getBgColor(8, 1), [22, 23, 24, 255]);\n  });\n\n  test('foreground 0-15 bright', async () => {\n    const theme: ITheme = {\n      brightBlack: '#010203',\n      brightRed: '#040506',\n      brightGreen: '#070809',\n      brightYellow: '#0a0b0c',\n      brightBlue: '#0d0e0f',\n      brightMagenta: '#101112',\n      brightCyan: '#131415',\n      brightWhite: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await ctx.value.proxy.write(`\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n  });\n\n  test('background 0-15 bright', async () => {\n    const theme: ITheme = {\n      brightBlack: '#010203',\n      brightRed: '#040506',\n      brightGreen: '#070809',\n      brightYellow: '#0a0b0c',\n      brightBlue: '#0d0e0f',\n      brightMagenta: '#101112',\n      brightCyan: '#131415',\n      brightWhite: '#161718'\n    };\n    await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n    await ctx.value.proxy.write(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);\n    await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);\n  });\n\n  test('foreground 16-255', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[38;5;${16 + y * 16 + x}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);\n      }\n    }\n  });\n\n  test('background 16-255', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[48;5;${16 + y * 16 + x}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);\n      }\n    }\n  });\n\n  test('foreground 16-255 inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[7;38;5;${16 + y * 16 + x}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);\n      }\n    }\n  });\n\n  test('background 16-255 inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[7;48;5;${16 + y * 16 + x}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);\n      }\n    }\n  });\n\n  test('foreground 16-255 invisible', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[8;38;5;${16 + y * 16 + x}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, 0, 255]);\n      }\n    }\n  });\n\n  test('background 16-255 invisible', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[8;48;5;${16 + y * 16 + x}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));\n    const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);\n    const firstCssColor = COLORS_16_TO_255[1];\n    const firstR = parseInt(firstCssColor.slice(1, 3), 16);\n    const firstG = parseInt(firstCssColor.slice(3, 5), 16);\n    const firstB = parseInt(firstCssColor.slice(5, 7), 16);\n    await pollFor(ctx.value.page, () => getBgColor(2, 1), [firstR, firstG, firstB, 255]);\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        await pollFor(ctx.value.page, () => getBgColor(x + 1, y + 1), [r, g, b, 255]);\n      }\n    }\n  });\n\n  test('foreground 16-255 dim', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[2;38;5;${16 + y * 16 + x}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        // It's difficult to assert the exact color due to rounding, just ensure the color differs\n        // to the regular color\n        await pollFor(ctx.value.page, async () => {\n          const c = await getCellColor(ctx.value, x + 1, y + 1);\n          return (\n            (c[0] === 0 || c[0] !== r) &&\n            (c[1] === 0 || c[1] !== g) &&\n            (c[2] === 0 || c[2] !== b)\n          );\n        }, true);\n      }\n    }\n  });\n\n  test('background 16-255 dim', async () => {\n    let data = '';\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        data += `\\x1b[2;48;5;${16 + y * 16 + x}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 240 / 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const cssColor = COLORS_16_TO_255[y * 16 + x];\n        const r = parseInt(cssColor.slice(1, 3), 16);\n        const g = parseInt(cssColor.slice(3, 5), 16);\n        const b = parseInt(cssColor.slice(5, 7), 16);\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);\n      }\n    }\n  });\n\n  test('foreground true color red', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[38;2;${i};0;0m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);\n      }\n    }\n  });\n\n  test('background true color red', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[48;2;${i};0;0m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);\n      }\n    }\n  });\n\n  test('foreground true color green', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[38;2;0;${i};0m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);\n      }\n    }\n  });\n\n  test('background true color green', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[48;2;0;${i};0m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);\n      }\n    }\n  });\n\n  test('foreground true color blue', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[38;2;0;0;${i}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);\n      }\n    }\n  });\n\n  test('background true color blue', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[48;2;0;0;${i}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);\n      }\n    }\n  });\n\n  test('foreground true color grey', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[38;2;${i};${i};${i}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);\n      }\n    }\n  });\n\n  test('background true color grey', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[48;2;${i};${i};${i}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);\n      }\n    }\n  });\n\n  test('foreground true color red inverse', async function(): Promise<void> {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;38;2;${i};0;0m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);\n      }\n    }\n  });\n\n  test('background true color red inverse', async function(): Promise<void> {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;48;2;${i};0;0m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);\n      }\n    }\n  });\n\n  test('foreground true color green inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;38;2;0;${i};0m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);\n      }\n    }\n  });\n\n  test('background true color green inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;48;2;0;${i};0m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);\n      }\n    }\n  });\n\n  test('foreground true color blue inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;38;2;0;0;${i}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);\n      }\n    }\n  });\n\n  test('background true color blue inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;48;2;0;0;${i}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);\n      }\n    }\n  });\n\n  test('foreground true color grey inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;38;2;${i};${i};${i}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);\n      }\n    }\n  });\n\n  test('background true color grey inverse', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[7;48;2;${i};${i};${i}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);\n      }\n    }\n  });\n\n  test('foreground true color grey invisible', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[8;38;2;${i};${i};${i}m \\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await ctx.value.proxy.write(data);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, 0, 255]);\n      }\n    }\n  });\n\n  test('background true color grey invisible', async () => {\n    let data = '';\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        data += `\\x1b[8;48;2;${i};${i};${i}m■\\x1b[0m`;\n      }\n      data += '\\r\\n';\n    }\n    await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));\n    const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);\n    await pollFor(ctx.value.page, () => getBgColor(2, 1), [1, 1, 1, 255]);\n    for (let y = 0; y < 16; y++) {\n      for (let x = 0; x < 16; x++) {\n        const i = y * 16 + x;\n        await pollFor(ctx.value.page, () => getBgColor(x + 1, y + 1), [i, i, i, 255]);\n      }\n    }\n  });\n\n  test.describe('minimumContrastRatio', async () => {\n    test('should adjust 0-15 colors on black background', async () => {\n      const theme: ITheme = {\n        black: '#2e3436',\n        red: '#cc0000',\n        green: '#4e9a06',\n        yellow: '#c4a000',\n        blue: '#3465a4',\n        magenta: '#75507b',\n        cyan: '#06989a',\n        white: '#d3d7cf',\n        brightBlack: '#555753',\n        brightRed: '#ef2929',\n        brightGreen: '#8ae234',\n        brightYellow: '#fce94f',\n        brightBlue: '#729fcf',\n        brightMagenta: '#ad7fa8',\n        brightCyan: '#34e2e2',\n        brightWhite: '#eeeeec'\n      };\n      await ctx.value.page.evaluate(`\n        window.term.options.theme = ${JSON.stringify(theme)};\n        window.term.options.minimumContrastRatio = 1;\n      `);\n      await ctx.value.proxy.write(\n        `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +\n        `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`\n      );\n      // Validate before minimumContrastRatio is applied\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x2e, 0x34, 0x36, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0xcc, 0x00, 0x00, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0x4e, 0x9a, 0x06, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0xc4, 0xa0, 0x00, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0x34, 0x65, 0xa4, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0x75, 0x50, 0x7b, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0x06, 0x98, 0x9a, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0xd3, 0xd7, 0xcf, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0x55, 0x57, 0x53, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0xef, 0x29, 0x29, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [0x8a, 0xe2, 0x34, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [0xfc, 0xe9, 0x4f, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [0x72, 0x9f, 0xcf, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [0xad, 0x7f, 0xa8, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]);\n      // Setting and check for minimum contrast values, note that these are not\n      // exact to the contrast ratio, if the increase luminance algorithm\n      // changes then these will probably fail\n      await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [176, 180, 180, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [238, 158, 158, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [152, 198, 110, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [208, 179, 49, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [161, 183, 215, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [191, 174, 194, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [110, 197, 198, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [211, 215, 207, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [183, 185, 183, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [249, 156, 156, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [138, 226, 52, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [252, 233, 79, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [154, 186, 221, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [203, 173, 199, 255]);\n      // Unchanged\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]);\n    });\n\n    test('should adjust 0-15 colors on white background', async () => {\n      const theme: ITheme = {\n        background: '#ffffff',\n        black: '#2e3436',\n        red: '#cc0000',\n        green: '#4e9a06',\n        yellow: '#c4a000',\n        blue: '#3465a4',\n        magenta: '#75507b',\n        cyan: '#06989a',\n        white: '#d3d7cf',\n        brightBlack: '#555753',\n        brightRed: '#ef2929',\n        brightGreen: '#8ae234',\n        brightYellow: '#fce94f',\n        brightBlue: '#729fcf',\n        brightMagenta: '#ad7fa8',\n        brightCyan: '#34e2e2',\n        brightWhite: '#eeeeec'\n      };\n      await ctx.value.page.evaluate(`\n        window.term.options.theme = ${JSON.stringify(theme)};\n        window.term.options.minimumContrastRatio = 1;\n      `);\n      await ctx.value.proxy.write(\n        `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +\n        `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`\n      );\n      // Validate before minimumContrastRatio is applied\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x2e, 0x34, 0x36, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0xcc, 0x00, 0x00, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0x4e, 0x9a, 0x06, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0xc4, 0xa0, 0x00, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0x34, 0x65, 0xa4, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0x75, 0x50, 0x7b, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0x06, 0x98, 0x9a, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0xd3, 0xd7, 0xcf, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0x55, 0x57, 0x53, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0xef, 0x29, 0x29, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [0x8a, 0xe2, 0x34, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [0xfc, 0xe9, 0x4f, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [0x72, 0x9f, 0xcf, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [0xad, 0x7f, 0xa8, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]);\n      // Setting and check for minimum contrast values, note that these are not\n      // exact to the contrast ratio, if the increase luminance algorithm\n      // changes then these will probably fail\n      await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [46, 52, 54, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [132, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [36, 72, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [72, 59, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [32, 64, 106, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [75, 51, 80, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0, 71, 72, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [64, 64, 63, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [61, 63, 59, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [125, 19, 19, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [40, 67, 13, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [67, 63, 19, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [45, 65, 87, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [81, 57, 78, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [13, 67, 67, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [64, 64, 64, 255]);\n    });\n\n    test('should enforce half the contrast for dim cells', async () => {\n      const theme: ITheme = {\n        background: '#ffffff',\n        black: '#2e3436',\n        red: '#cc0000',\n        green: '#4e9a06',\n        yellow: '#c4a000',\n        blue: '#3465a4',\n        magenta: '#75507b',\n        cyan: '#06989a',\n        white: '#d3d7cf',\n        brightBlack: '#555753',\n        brightRed: '#ef2929',\n        brightGreen: '#8ae234',\n        brightYellow: '#fce94f',\n        brightBlue: '#729fcf',\n        brightMagenta: '#ad7fa8',\n        brightCyan: '#34e2e2',\n        brightWhite: '#eeeeec'\n      };\n      await ctx.value.page.evaluate(`\n        window.term.options.theme = ${JSON.stringify(theme)};\n        window.term.options.minimumContrastRatio = 1;\n      `);\n      await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(\n        '\\x1b[2m' +\n        `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +\n        `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`\n      ));\n      // Validate before minimumContrastRatio is applied\n      const marginOfError = 1;\n      const background: [number, number, number, number] = [255, 255, 255, 255];\n      const compositeOverBackground = (fg: [number, number, number, number]): [number, number, number, number] => {\n        const alpha = fg[3] / 255;\n        const inv = 1 - alpha;\n        return [\n          Math.round(fg[0] * alpha + background[0] * inv),\n          Math.round(fg[1] * alpha + background[1] * inv),\n          Math.round(fg[2] * alpha + background[2] * inv),\n          255\n        ];\n      };\n      const getFgColor = async (col: number, row: number): Promise<[number, number, number, number]> => compositeOverBackground(await getCellDomForegroundColor(ctx.value, col, row));\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]);\n      // Setting and check for minimum contrast values, note that these are not\n      // exact to the contrast ratio, if the increase luminance algorithm\n      // changes then these will probably fail\n      await waitForRenderAfter(ctx.value, () => ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`));\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 1), [150, 153, 154, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 1), [229, 127, 127, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 1), [63, 124, 4, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 1), [127, 104, 0, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 1), [153, 178, 209, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 1), [186, 167, 189, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 1), [4, 122, 124, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 1), [110, 112, 108, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 2), [170, 171, 169, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 2), [215, 36, 36, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 2), [72, 117, 25, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 2), [117, 109, 36, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 2), [72, 103, 135, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 2), [125, 91, 121, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 2), [25, 117, 117, 255]);\n      await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 2), [111, 111, 110, 255]);\n    });\n  });\n\n  test.describe('selectionBackground', async () => {\n    test('should resolve the inverse foreground color based on the original background color, not the selection', async () => {\n      const theme: ITheme = {\n        foreground: '#FF0000',\n        background: '#00FF00',\n        selectionBackground: '#0000FF'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.write( ` ■\\x1b[7m■\\x1b[0m`);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 255, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]);\n      await ctx.value.proxy.selectAll();\n      frameDetails = undefined;\n      // Selection only cell needs to be first to ensure renderer has kicked in\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]);\n    });\n  });\n\n  test.describe('selectionInactiveBackground', async () => {\n    test('should render the the inactive selection when not focused', async () => {\n      const theme: ITheme = {\n        selectionBackground: '#FF000080',\n        selectionInactiveBackground: '#0000FF80'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.focus();\n      // Check both the cursor line and another line\n      await ctx.value.proxy.writeln('_ ');\n      await ctx.value.proxy.write('_ ');\n      await ctx.value.proxy.selectAll();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [128, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [128, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [128, 0, 0, 255]);\n      await ctx.value.page.evaluate(`document.activeElement.blur()`);\n      frameDetails = undefined;\n      // Selection only cell needs to be first to ensure renderer has kicked in\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0, 0, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0, 0, 128, 255]);\n    });\n  });\n\n  ctx.skipDomExceptions ? test.describe.skip : test.describe('selection blending', () => {\n    test('background', async () => {\n      const theme: ITheme = {\n        red: '#CC0000',\n        selectionBackground: '#FFFFFF'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.focus();\n      await ctx.value.proxy.writeln('\\x1b[41m red bg\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[7m inverse\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[31;7m red fg inverse\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[48:2:0:204:0:0m red truecolor bg\\x1b[0m');\n      await ctx.value.proxy.selectAll();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [230, 128, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [255, 255, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [230, 128, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 4), [230, 128, 128, 255]);\n    });\n    test('powerline decorative symbols', async () => {\n      const theme: ITheme = {\n        red: '#CC0000',\n        green: '#00CC00',\n        selectionBackground: '#FFFFFF'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.focus();\n      await ctx.value.proxy.writeln('\\u{E0B4} plain\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[31;42m\\u{E0B4} red fg green bg\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[32;41m\\u{E0B4} green fg red bg\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[31;42;7m\\u{E0B4} red fg green bg inverse\\x1b[0m');\n      await ctx.value.proxy.writeln('\\x1b[32;41;7m\\u{E0B4} green fg red bg inverse\\x1b[0m');\n      await ctx.value.proxy.selectAll();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 255, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [230, 128, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [128, 230, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 4), [128, 230, 128, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 5), [230, 128, 128, 255]);\n    });\n  });\n\n  test.describe('allowTransparency', async () => {\n    test.beforeEach(() => ctx.value.page.evaluate(`term.options.allowTransparency = true`));\n\n    test('transparent background inverse', async () => {\n      const theme: ITheme = {\n        background: '#ff000080'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      const data = `\\x1b[7m■\\x1b[0m`;\n      await ctx.value.proxy.write( data);\n      // Inverse background should be opaque\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n    });\n  });\n\n  test.describe('selectionForeground', () => {\n    test('transparent background inverse', async () => {\n      const theme: ITheme = {\n        selectionForeground: '#ff0000'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      const data = `\\x1b[7m■\\x1b[0m`;\n      await ctx.value.proxy.write( data);\n      await ctx.value.proxy.selectAll();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n    });\n  });\n\n  test.describe('decoration color overrides', async () => {\n    test('foregroundColor', async () => {\n      await ctx.value.page.evaluate(`\n        const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);\n        window.term.registerDecoration({\n          marker,\n          foregroundColor: '#ff0000',\n          backgroundColor: '#0000ff'\n        });\n      `);\n      const data = `■`;\n      await ctx.value.proxy.write( data);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n    });\n    test('foregroundColor should ignore inverse', async () => {\n      await ctx.value.page.evaluate(`\n        const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);\n        window.term.registerDecoration({\n          marker,\n          foregroundColor: '#ff0000',\n          backgroundColor: '#0000ff'\n        });\n      `);\n      const data = `\\x1b[7m■\\x1b[0m`;\n      await ctx.value.proxy.write( data);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n    });\n    test('foregroundColor should ignore inverse (only fg on decoration)', async () => {\n      await ctx.value.page.evaluate(`\n        const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);\n        window.term.registerDecoration({\n          marker,\n          width: 2,\n          foregroundColor: '#ff0000'\n        });\n      `);\n      const data = `\\x1b[7m■ \\x1b[0m`;\n      await ctx.value.proxy.write( data);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); // inverse foreground of '■' should be decoration fg override\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground\n    });\n    test('backgroundColor', async () => {\n      await ctx.value.page.evaluate(`\n        const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);\n        window.term.registerDecoration({\n          marker,\n          foregroundColor: '#ff0000',\n          backgroundColor: '#0000ff'\n        });\n      `);\n      const data = ` `;\n      await ctx.value.proxy.write( data);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);\n    });\n    test('backgroundColor should ignore inverse', async () => {\n      await ctx.value.page.evaluate(`\n        const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);\n        window.term.registerDecoration({\n          marker,\n          foregroundColor: '#ff0000',\n          backgroundColor: '#0000ff'\n        });\n      `);\n      const data = `\\x1b[7m \\x1b[0m`;\n      await ctx.value.proxy.write( data);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);\n    });\n    test('backgroundColor should ignore inverse (only bg on decoration)', async () => {\n      const data = `\\x1b[7m■ \\x1b[0m`;\n      await ctx.value.proxy.write( data);\n      await ctx.value.page.evaluate(`\n        const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);\n        window.term.registerDecoration({\n          marker,\n          width: 2,\n          backgroundColor: '#0000ff'\n        });\n      `);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '■' should be default\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override\n    });\n  });\n\n  test.describe('regression tests', () => {\n    test('#4736: inactive selection background should replace regular cell background color', async () => {\n      const theme: ITheme = {\n        selectionBackground: '#FF0000',\n        selectionInactiveBackground: '#0000FF'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.writeln(' ');\n      await ctx.value.proxy.writeln(' O ');\n      await ctx.value.proxy.writeln(' ');\n      await ctx.value.proxy.focus();\n      await ctx.value.proxy.selectAll();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 3), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 3), [255, 0, 0, 255]);\n      await ctx.value.proxy.blur();\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0, 0, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 3), [0, 0, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 3), [0, 0, 255, 255]);\n    });\n    test('#4758: multiple invisible text characters without SGR change should not be rendered', async () => {\n      // Regression test: #4758 when multiple invisible characters are used\n      await ctx.value.proxy.writeln(`■\\x1b[8m■■`);\n      // Full refresh as the before result is the same as after\n      const rows = await ctx.value.proxy.rows;\n      await waitForRenderAfter(ctx.value, () => ctx.value.proxy.refresh(0, rows - 1));\n      await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 1, 1), '■');\n      await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 2, 1), ' ');\n      await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 3, 1), ' ');\n    });\n    // HACK: It's not clear why DOM is failing here\n    (ctx.skipDomExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on inverse text', async () => {\n      const theme: ITheme = {\n        foreground: '#aaaaaa',\n        background: '#333333'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.write(`\\x1b[7m■■`);\n      // Validate before minimumContrastRatio is applied\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [51, 51, 51, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [51, 51, 51, 255]);\n      await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]);\n    });\n    test('#4759: minimum contrast ratio should be respected on selected inverse text', async () => {\n      const theme: ITheme = {\n        foreground: '#777777',\n        background: '#555555',\n        selectionBackground: '#666666' // Slightly more contrast needed for selection\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.write(`\\x1b[7m■■`);\n      await ctx.value.proxy.selectAll();\n      // Validate before minimumContrastRatio is applied\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [85, 85, 85, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [85, 85, 85, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]);\n      await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);\n      await ctx.value.proxy.selectAll();\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 255, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]);\n    });\n    test('#4773: block cursor should render when the cell is selected', async () => {\n      const theme: ITheme = {\n        cursor: '#0000FF',\n        selectionBackground: '#FF0000'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.focus();\n      await ctx.value.proxy.selectAll();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);\n    });\n    test('#4799: cursor should be in the correct position', async () => {\n      const theme: ITheme = {\n        cursor: '#0000FF'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      for (let index = 0; index < 160; index++) {\n        await ctx.value.proxy.writeln(``);\n      }\n      await ctx.value.proxy.focus();\n      await ctx.value.proxy.write('\\x1b[A');\n      await ctx.value.proxy.write('\\x1b[A');\n      await ctx.value.proxy.scrollLines(-2);\n      const rows = await ctx.value.proxy.rows;\n      // block cursor style\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows), [0, 0, 255, 255]);\n      await ctx.value.proxy.blur();\n      frameDetails = undefined;\n      // outlink cursor style\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows), [0, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows, CellColorPosition.FIRST), [0, 0, 255, 255]);\n    });\n    test('#4917 The selection should not be displayed if it is not within the scope of the viewport.', async () => {\n      const theme: ITheme = {\n        selectionBackground: '#FF0000'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      for (let index = 0; index < 160; index++) {\n        await ctx.value.proxy.writeln(``);\n      }\n      await ctx.value.proxy.scrollToBottom();\n      const rows = await ctx.value.proxy.buffer.active.length;\n      await ctx.value.proxy.selectLines(rows - 1, rows - 1);\n      await ctx.value.proxy.scrollLines(-2);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);\n    });\n    test('#5241 cursor with alpha should blend color with background color', async () => {\n      const theme: ITheme = {\n        cursor: '#FF000080'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.focus();\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]);\n    });\n    test('#5241 cursorAccent with alpha should blend color with background color', async () => {\n      const theme: ITheme = {\n        cursorAccent: '#FF000080'\n      };\n      await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n      await ctx.value.proxy.focus();\n      await ctx.value.proxy.write('■');\n      await ctx.value.proxy.write('\\x1b[1D');\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]);\n    });\n  });\n\n  // TODO: These tests are a little too flaky atm\n  test.describe.skip('synchronized output', () => {\n    test.beforeEach(async () => {\n      const theme: ITheme = {\n        background: '#000000FF',\n\n        red: '#FF0000FF',\n        green: '#00FF00FF',\n        blue: '#0000FFFF'\n      };\n      await ctx.value.page.evaluate(`\n        window.term.options.theme = ${JSON.stringify(theme)};\n        window.term.options.cursorStyle = 'underline';\n      `);\n    });\n    test('defers rendering until ESU', async () => {\n      await ctx.value.proxy.write('\\x1b[?2026h'); // BSU\n      await ctx.value.proxy.write('\\x1b[31m■');\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {\n        equalityFn: (a, b) => {\n          return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);\n        }\n      });\n      await ctx.value.proxy.write('\\x1b[?2026l'); // ESU\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n    });\n\n    test('batches multiple writes', async () => {\n      await ctx.value.proxy.write('\\x1b[?2026h'); // BSU\n      await ctx.value.proxy.write('\\x1b[31m■\\x1b[32m■\\x1b[34m■');\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {\n        equalityFn: (a, b) => {\n          return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);\n        }\n      });\n      await ctx.value.proxy.write('\\x1b[?2026l'); // ESU\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 255, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 255, 255]);\n    });\n\n    test('nested BSU is idempotent', async () => {\n      await ctx.value.proxy.write('\\x1b[?2026h'); // BSU\n      await ctx.value.proxy.write('\\x1b[31m■');\n      await ctx.value.proxy.write('\\x1b[?2026h'); // BSU\n      await ctx.value.proxy.write('\\x1b[32m■');\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {\n        equalityFn: (a, b) => {\n          return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);\n        }\n      });\n      await ctx.value.proxy.write('\\x1b[?2026l'); // ESU\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 255, 0, 255]);\n    });\n\n    test('timeout flushes without ESU', async () => {\n      await ctx.value.proxy.write('\\x1b[?2026h'); // BSU\n      await ctx.value.proxy.write('\\x1b[31m■');\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {\n        equalityFn: (a, b) => {\n          return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);\n        }\n      });\n      await ctx.value.page.waitForTimeout(1000); // Timeout hard coded\n      frameDetails = undefined;\n      await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);\n    });\n  });\n}\n\nasync function waitForRender(ctx: ITestContext): Promise<void> {\n  await new Promise<void>(resolve => {\n    const disposable = ctx.proxy.onRender(() => {\n      disposable.dispose();\n      resolve();\n    });\n  });\n}\n\nasync function waitForRenderAfter(ctx: ITestContext, action: () => Promise<void>): Promise<void> {\n  const renderPromise = waitForRender(ctx);\n  await action();\n  await renderPromise;\n  frameDetails = undefined;\n}\n\nasync function pollForCellColorFresh(ctx: ITestContext, col: number, row: number, expected: [number, number, number, number], position: CellColorPosition = CellColorPosition.CENTER): Promise<void> {\n  await pollFor(ctx.page, () => getCellColor(ctx, col, row, position), expected, async () => {\n    frameDetails = undefined;\n  });\n}\n\ninterface ICellDomInfo {\n  fg: string;\n  bg: string;\n  char: string;\n}\n\nfunction parseCssColor(value: string): [number, number, number, number] {\n  if (value === 'transparent') {\n    return [0, 0, 0, 0];\n  }\n  const match = value.match(/^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([0-9.]+))?\\)$/);\n  if (!match) {\n    throw new Error(`Unsupported color format: ${value}`);\n  }\n  const r = Number(match[1]);\n  const g = Number(match[2]);\n  const b = Number(match[3]);\n  const a = match[4] === undefined ? 255 : Math.round(Number(match[4]) * 255);\n  return [r, g, b, a];\n}\n\nasync function getCellDomInfo(ctx: ITestContext, col: number, row: number): Promise<ICellDomInfo> {\n  const info = await ctx.page.evaluate(({ col, row }) => {\n    const rows = document.querySelectorAll('#terminal-container .xterm-rows > div');\n    const rowEl = rows[row - 1];\n    if (!rowEl) {\n      return undefined;\n    }\n    let currentCol = 1;\n    for (const el of Array.from(rowEl.children)) {\n      const text = el.textContent ?? '';\n      const chars = Array.from(text);\n      const nextCol = currentCol + chars.length;\n      if (col >= currentCol && col < nextCol) {\n        const style = window.getComputedStyle(el as HTMLElement);\n        return {\n          fg: style.color,\n          bg: style.backgroundColor,\n          char: chars[col - currentCol] ?? ''\n        };\n      }\n      currentCol = nextCol;\n    }\n    return undefined;\n  }, { col, row });\n\n  if (!info) {\n    throw new Error(`Cell not found: ${col},${row}`);\n  }\n  return info;\n}\n\nasync function getCellDomBackgroundColor(ctx: ITestContext, col: number, row: number): Promise<[number, number, number, number]> {\n  const info = await getCellDomInfo(ctx, col, row);\n  return parseCssColor(info.bg);\n}\n\nasync function getCellDomForegroundColor(ctx: ITestContext, col: number, row: number): Promise<[number, number, number, number]> {\n  const info = await getCellDomInfo(ctx, col, row);\n  return parseCssColor(info.fg);\n}\n\nasync function getCellDomChar(ctx: ITestContext, col: number, row: number): Promise<string> {\n  const info = await getCellDomInfo(ctx, col, row);\n  return info.char;\n}\n\nenum CellColorPosition {\n  CENTER = 0,\n  FIRST = 1\n}\n\n/**\n * Injects shared renderer tests where it's required to re-initialize the terminal for each test.\n * This is much slower than just calling `Terminal.reset` but testing some features needs this\n * treatment.\n */\nexport function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext, setupCb: () => Promise<void> | void): void {\n  const setupTests = ({ shadowDom }: { shadowDom: boolean }): void => {\n    test.beforeEach(async () => {\n      // Recreate terminal\n      await openTerminal(ctx.value, {}, { useShadowDom: shadowDom });\n      await ctx.value.page.evaluate(`\n        window.term.options.minimumContrastRatio = 1;\n        window.term.options.allowTransparency = false;\n        window.term.options.theme = undefined;\n      `);\n      await setupCb();\n      // Clear the cached screenshot before each test\n      frameDetails = undefined;\n    });\n    test.describe('regression tests', () => {\n      test('#4790: cursor should not be displayed before focusing', async () => {\n        const theme: ITheme = {\n          cursor: '#0000FF'\n        };\n        await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);\n        await ctx.value.proxy.focus();\n        frameDetails = undefined;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);\n        await ctx.value.proxy.blur();\n        frameDetails = undefined;\n        await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);\n      });\n    });\n  };\n\n  test.describe('standalone tests', () => {\n    setupTests({ shadowDom: false });\n  });\n  test.describe('standalone tests (Shadow dom)', () => {\n    setupTests({ shadowDom: true });\n  });\n}\n\n/**\n * Gets the color of the pixel in the center of a cell.\n * @param ctx The test context.\n * @param col The 1-based column index to get the color for.\n * @param row The 1-based row index to get the color for.\n */\nasync function getCellColor(ctx: ITestContext, col: number, row: number, position: CellColorPosition = CellColorPosition.CENTER): Promise<[red: number, green: number, blue: number, alpha: number]> {\n  frameDetails ??= await getFrameDetails(ctx);\n  switch (position) {\n    case CellColorPosition.CENTER:\n      return getCellColorInner(frameDetails, col, row);\n    case CellColorPosition.FIRST:\n      return getCellColorFirstPoint(frameDetails, col, row);\n  }\n}\n\nlet frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined;\nasync function getFrameDetails(ctx: ITestContext): Promise<{ cols: number, rows: number, decoded: IImage32 }> {\n  const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-esbuild-test/playwright/screenshot.png' } : undefined;\n  const buffer = await ctx.page.locator('#terminal-container .xterm-screen').screenshot(screenshotOptions);\n  frameDetails = {\n    cols: await ctx.proxy.cols,\n    rows: await ctx.proxy.rows,\n    decoded: (await decodePng(new Uint8Array(buffer), { force32: true })).image\n  };\n  return frameDetails;\n}\n\nfunction getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] {\n  const cellSize = {\n    width: frameDetails.decoded.width / frameDetails.cols,\n    height: frameDetails.decoded.height / frameDetails.rows\n  };\n  const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width);\n  const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height);\n  const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */;\n  return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number];\n}\n\nfunction getCellColorFirstPoint(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] {\n  const cellSize = {\n    width: frameDetails.decoded.width / frameDetails.cols,\n    height: frameDetails.decoded.height / frameDetails.rows\n  };\n  const x = Math.floor((col - 1/* 1- to 0-based index */) * cellSize.width);\n  const y = Math.floor((row - 1/* 1- to 0-based index */) * cellSize.height);\n  const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */;\n  return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number];\n}\n\nconst COLORS_16_TO_255 = [\n  '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',\n  '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',\n  '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff',\n  '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af',\n  '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f',\n  '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff',\n  '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af',\n  '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f',\n  '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff',\n  '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af',\n  '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f',\n  '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff',\n  '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af',\n  '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e',\n  '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee'\n];\n"
  },
  {
    "path": "test/playwright/Terminal.test.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { test } from '@playwright/test';\nimport { deepStrictEqual, notStrictEqual, strictEqual } from 'assert';\nimport { ITestContext, createTestContext, openTerminal, pollFor, timeout } from './TestUtils';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\n\nlet ctx: ITestContext;\ntest.beforeAll(async ({ browser }) => {\n  ctx = await createTestContext(browser);\n  await openTerminal(ctx);\n});\ntest.afterAll(async () => await ctx.page.close());\n\n\ntest.describe('API Integration Tests', () => {\n  test('Default options', async () => {\n    strictEqual(await ctx.proxy.cols, 80);\n    strictEqual(await ctx.proxy.rows, 24);\n  });\n\n  test('Proposed API check', async () => {\n    await openTerminal(ctx, { allowProposedApi: false }, { loadUnicodeGraphemesAddon: false });\n    await ctx.page.evaluate(`\n      try {\n        window.term.unicode;\n      } catch (e) {\n        window.throwMessage = e.message;\n      }\n    `);\n    await pollFor(ctx.page, 'window.throwMessage', 'You must set the allowProposedApi option to true to use proposed API');\n  });\n\n  test('write', async () => {\n    await openTerminal(ctx);\n    await ctx.proxy.write('foo');\n    await ctx.proxy.write('bar');\n    await ctx.proxy.write('文');\n    strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(true), 'foobar文');\n  });\n\n  test('write with callback', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      window.term.write('foo', () => { window.__x = 'a'; });\n      window.term.write('bar', () => { window.__x += 'b'; });\n      window.term.write('文', () => { window.__x += 'c'; });\n    `);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');\n    await pollFor(ctx.page, `window.__x`, 'abc');\n  });\n\n  test('write - bytes (UTF8)', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      window.term.write(new Uint8Array([102, 111, 111])); // foo\n      window.term.write(new Uint8Array([98, 97, 114])); // bar\n      window.term.write(new Uint8Array([230, 150, 135])); // 文\n    `);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');\n  });\n\n  test('write - bytes (UTF8) with callback', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      window.term.write(new Uint8Array([102, 111, 111]), () => { window.__x = 'A'; }); // foo\n      window.term.write(new Uint8Array([98, 97, 114]), () => { window.__x += 'B'; }); // bar\n      window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; }); // 文\n    `);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');\n    await pollFor(ctx.page, `window.__x`, 'ABC');\n  });\n\n  test('writeln', async () => {\n    await openTerminal(ctx);\n    await ctx.proxy.writeln('foo');\n    await ctx.proxy.writeln('bar');\n    await ctx.proxy.writeln('文');\n    strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(true), 'foo');\n    strictEqual(await (await ctx.proxy.buffer.active.getLine(1))!.translateToString(true), 'bar');\n    strictEqual(await (await ctx.proxy.buffer.active.getLine(2))!.translateToString(true), '文');\n  });\n\n  test('writeln with callback', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      window.term.writeln('foo', () => { window.__x = '1'; });\n      window.term.writeln('bar', () => { window.__x += '2'; });\n      window.term.writeln('文', () => { window.__x += '3'; });\n    `);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');\n    await pollFor(ctx.page, `window.__x`, '123');\n  });\n\n  test('writeln - bytes (UTF8)', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      window.term.writeln(new Uint8Array([102, 111, 111]));\n      window.term.writeln(new Uint8Array([98, 97, 114]));\n      window.term.writeln(new Uint8Array([230, 150, 135]));\n    `);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');\n  });\n\n  test('paste', async () => {\n    await openTerminal(ctx);\n    const calls: string[] = [];\n    ctx.proxy.onData(e => calls.push(e));\n    await ctx.proxy.paste('foo');\n    await ctx.proxy.paste('\\r\\nfoo\\nbar\\r');\n    await ctx.proxy.write('\\x1b[?2004h');\n    await ctx.proxy.paste('foo');\n    await ctx.proxy.setOption('ignoreBracketedPasteMode', true);\n    await ctx.proxy.paste('check_mode');\n    deepStrictEqual(calls, ['foo', '\\rfoo\\rbar\\r', '\\x1b[200~foo\\x1b[201~', 'check_mode']);\n  });\n\n  test('clear', async () => {\n    await openTerminal(ctx, { rows: 5 });\n    await ctx.page.evaluate(`\n      window.term.write('test0');\n      window.parsed = 0;\n      for (let i = 1; i < 10; i++) {\n        window.term.write('\\\\n\\\\rtest' + i, () => window.parsed++);\n      }\n    `);\n    await pollFor(ctx.page, `window.parsed`, 9);\n    await ctx.page.evaluate(`window.term.clear()`);\n    await pollFor(ctx.page, `window.term.buffer.active.length`, 5);\n    await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'test9');\n    for (let i = 1; i < 5; i++) {\n      await pollFor(ctx.page, `window.term.buffer.active.getLine(${i}).translateToString(true)`, '');\n    }\n  });\n\n  test.describe('options', () => {\n    test('getter', async () => {\n      await openTerminal(ctx);\n      strictEqual(await ctx.page.evaluate(`window.term.options.cols`), 80);\n      strictEqual(await ctx.page.evaluate(`window.term.options.rows`), 24);\n    });\n    test('setter', async () => {\n      await openTerminal(ctx);\n      try {\n        await ctx.page.evaluate('window.term.options.cols = 40');\n        test.fail();\n      } catch {}\n      try {\n        await ctx.page.evaluate('window.term.options.rows = 20');\n        test.fail();\n      } catch {}\n      await ctx.page.evaluate('window.term.options.scrollback = 1');\n      strictEqual(await ctx.page.evaluate(`window.term.options.scrollback`), 1);\n      await ctx.page.evaluate(`\n        window.term.options = {\n          fontSize: 30,\n          fontFamily: 'Arial'\n        };\n      `);\n      strictEqual(await ctx.page.evaluate(`window.term.options.fontSize`), 30);\n      strictEqual(await ctx.page.evaluate(`window.term.options.fontFamily`), 'Arial');\n    });\n    test('object.keys return the correct number of options', async () => {\n      await openTerminal(ctx);\n      notStrictEqual(await ctx.page.evaluate(`Object.keys(window.term.options).length`), 0);\n    });\n  });\n\n  test.describe('renderer', () => {\n    test('foreground', async () => {\n      await openTerminal(ctx);\n      await ctx.proxy.write('\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7');\n      await pollFor(ctx.page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);\n      deepStrictEqual(await ctx.page.evaluate(`\n        [\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(2)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(3)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(4)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(5)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(6)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(7)').className\n        ]\n      `), [\n        'xterm-fg-0',\n        'xterm-fg-1',\n        'xterm-fg-2',\n        'xterm-fg-3',\n        'xterm-fg-4',\n        'xterm-fg-5',\n        'xterm-fg-6'\n      ]);\n    });\n\n    test('background', async () => {\n      await openTerminal(ctx);\n      await ctx.proxy.write('\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7');\n      await pollFor(ctx.page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);\n      deepStrictEqual(await ctx.page.evaluate(`\n        [\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(2)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(3)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(4)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(5)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(6)').className,\n          document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(7)').className\n        ]\n      `), [\n        'xterm-bg-0',\n        'xterm-bg-1',\n        'xterm-bg-2',\n        'xterm-bg-3',\n        'xterm-bg-4',\n        'xterm-bg-5',\n        'xterm-bg-6'\n      ]);\n    });\n  });\n\n  test('selection', async () => {\n    await openTerminal(ctx, { rows: 5, cols: 5 });\n    await ctx.proxy.write(`\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz`);\n    strictEqual(await ctx.page.evaluate(`window.term.hasSelection()`), false);\n    strictEqual(await ctx.page.evaluate(`window.term.getSelection()`), '');\n    deepStrictEqual(await ctx.page.evaluate(`window.term.getSelectionPosition()`), undefined);\n    await ctx.page.evaluate(`window.term.selectAll()`);\n    strictEqual(await ctx.page.evaluate(`window.term.hasSelection()`), true);\n    if (process.platform === 'win32') {\n      strictEqual(await ctx.page.evaluate(`window.term.getSelection()`), '\\r\\n\\r\\nfoo\\r\\n\\r\\nbar\\r\\n\\r\\nbaz');\n    } else {\n      strictEqual(await ctx.page.evaluate(`window.term.getSelection()`), '\\n\\nfoo\\n\\nbar\\n\\nbaz');\n    }\n    deepStrictEqual(await ctx.page.evaluate(`window.term.getSelectionPosition()`), { start: { x: 0, y: 0 }, end: { x: 5, y: 6 } });\n    await ctx.page.evaluate(`window.term.clearSelection()`);\n    strictEqual(await ctx.page.evaluate(`window.term.hasSelection()`), false);\n    strictEqual(await ctx.page.evaluate(`window.term.getSelection()`), '');\n    deepStrictEqual(await ctx.page.evaluate(`window.term.getSelectionPosition()`), undefined);\n    await ctx.page.evaluate(`window.term.select(1, 2, 2)`);\n    strictEqual(await ctx.page.evaluate(`window.term.hasSelection()`), true);\n    strictEqual(await ctx.page.evaluate(`window.term.getSelection()`), 'oo');\n    deepStrictEqual(await ctx.page.evaluate(`window.term.getSelectionPosition()`), { start: { x: 1, y: 2 }, end: { x: 3, y: 2 } });\n  });\n\n  test('focus, blur', async () => {\n    await openTerminal(ctx);\n    strictEqual(await ctx.page.evaluate(`document.activeElement.className`), '');\n    await ctx.page.evaluate(`window.term.focus()`);\n    strictEqual(await ctx.page.evaluate(`document.activeElement.className`), 'xterm-helper-textarea');\n    await ctx.page.evaluate(`window.term.blur()`);\n    strictEqual(await ctx.page.evaluate(`document.activeElement.className`), '');\n  });\n\n  test.describe('loadAddon', () => {\n    test('constructor', async () => {\n      await openTerminal(ctx, { cols: 5 });\n      await ctx.page.evaluate(`\n        window.cols = 0;\n        window.term.loadAddon({\n          activate: (t) => window.cols = t.cols,\n          dispose: () => {}\n        });\n      `);\n      strictEqual(await ctx.page.evaluate(`window.cols`), 5);\n    });\n\n    test('dispose (addon)', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.disposeCalled = false\n        window.addon = {\n          activate: () => {},\n          dispose: () => window.disposeCalled = true\n        };\n        window.term.loadAddon(window.addon);\n      `);\n      strictEqual(await ctx.page.evaluate(`window.disposeCalled`), false);\n      await ctx.page.evaluate(`window.addon.dispose()`);\n      strictEqual(await ctx.page.evaluate(`window.disposeCalled`), true);\n    });\n\n    test('dispose (terminal)', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.disposeCalled = false\n        window.term.loadAddon({\n          activate: () => {},\n          dispose: () => window.disposeCalled = true\n        });\n      `);\n      strictEqual(await ctx.page.evaluate(`window.disposeCalled`), false);\n      await ctx.page.evaluate(`window.term.dispose()`);\n      strictEqual(await ctx.page.evaluate(`window.disposeCalled`), true);\n    });\n  });\n\n  test.describe('Events', () => {\n    test('onCursorMove', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.callCount = 0;\n        window.term.onCursorMove(e => window.callCount++);\n        window.term.write('foo');\n      `);\n      await pollFor(ctx.page, `window.callCount`, 1);\n      await ctx.page.evaluate(`window.term.write('bar')`);\n      await pollFor(ctx.page, `window.callCount`, 2);\n    });\n\n    test('onData', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onData(e => calls.push(e));\n      `);\n      await ctx.page.type('.xterm-helper-textarea', 'foo');\n      deepStrictEqual(await ctx.page.evaluate(`window.calls`), ['f', 'o', 'o']);\n    });\n\n    test('onKey', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onKey(e => calls.push(e.key));\n      `);\n      await ctx.page.type('.xterm-helper-textarea', 'foo');\n      deepStrictEqual(await ctx.page.evaluate(`window.calls`), ['f', 'o', 'o']);\n    });\n\n    test('onLineFeed', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.callCount = 0;\n        window.term.onLineFeed(() => callCount++);\n        window.term.writeln('foo');\n      `);\n      await pollFor(ctx.page, `window.callCount`, 1);\n      await ctx.page.evaluate(`window.term.writeln('bar')`);\n      await pollFor(ctx.page, `window.callCount`, 2);\n    });\n\n    test('onScroll', async () => {\n      await openTerminal(ctx, { rows: 5 });\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onScroll(e => window.calls.push(e));\n        for (let i = 0; i < 4; i++) {\n          window.term.writeln('foo');\n        }\n      `);\n      await pollFor(ctx.page, `window.calls`, []);\n      await ctx.page.evaluate(`window.term.writeln('bar')`);\n      await pollFor(ctx.page, `window.calls`, [1]);\n      await ctx.page.evaluate(`window.term.writeln('baz')`);\n      await pollFor(ctx.page, `window.calls`, [1, 2]);\n    });\n\n    test.describe('onSelectionChange', () => {\n      let callCount: number;\n\n      test.beforeEach(async () => {\n        await openTerminal(ctx);\n        callCount = 0;\n        ctx.proxy.onSelectionChange(() => callCount++);\n      });\n\n      test('should fire for programmatic selection changes', async () => {\n        strictEqual(callCount, 0);\n        await ctx.proxy.selectAll();\n        strictEqual(callCount, 1);\n        await ctx.proxy.clearSelection();\n        strictEqual(callCount, 2);\n      });\n\n      test('should fire on mousedown when clearing selection', async () => {\n        await ctx.proxy.write('foo bar baz');\n        await ctx.proxy.selectAll();\n        strictEqual(callCount, 1);\n\n        const dims = (await ctx.proxy.dimensions)!;\n        const termRect: any = await ctx.page.evaluate(`window.term.element.getBoundingClientRect()`);\n        const x = termRect.left + dims.css.cell.width * 5;\n        const y = termRect.top + dims.css.cell.height * 0.5;\n        await ctx.page.mouse.click(x, y);\n\n        await pollFor(ctx.page, () => callCount, 2);\n      });\n\n      test('should not fire on mousedown when no prior selection', async () => {\n        await ctx.proxy.write('foo bar baz');\n        strictEqual(callCount, 0);\n\n        const dims = (await ctx.proxy.dimensions)!;\n        const termRect: any = await ctx.page.evaluate(`window.term.element.getBoundingClientRect()`);\n        const x = termRect.left + dims.css.cell.width * 5;\n        const y = termRect.top + dims.css.cell.height * 0.5;\n        await ctx.page.mouse.click(x, y);\n        await timeout(20);\n\n        strictEqual(callCount, 0);\n      });\n\n      test('should fire once on mousedown to clear, and again on mouseup after drag', async () => {\n        await ctx.proxy.write('foo bar baz');\n        await ctx.proxy.selectAll();\n        strictEqual(callCount, 1);\n\n        const dims = (await ctx.proxy.dimensions)!;\n        const termRect: any = await ctx.page.evaluate(`window.term.element.getBoundingClientRect()`);\n        const startX = termRect.left + dims.css.cell.width * 0.5;\n        const endX = termRect.left + dims.css.cell.width * 5;\n        const y = termRect.top + dims.css.cell.height * 0.5;\n\n        await ctx.page.mouse.move(startX, y);\n        await ctx.page.mouse.down();\n        await pollFor(ctx.page, () => callCount, 2);\n\n        await ctx.page.mouse.move(endX, y);\n        strictEqual(callCount, 2);\n\n        await ctx.page.mouse.up();\n        await pollFor(ctx.page, () => callCount, 3);\n      });\n    });\n\n    test('onRender', async () => {\n      await openTerminal(ctx);\n      await timeout(20); // Ensure all init events are fired\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onRender(e => window.calls.push([e.start, e.end]));\n      `);\n      await pollFor(ctx.page, `window.calls`, []);\n      await ctx.page.evaluate(`window.term.write('foo')`);\n      await pollFor(ctx.page, `window.calls`, [[0, 0]]);\n      await ctx.page.evaluate(`window.term.write('bar\\\\n\\\\nbaz')`);\n      await pollFor(ctx.page, `window.calls`, [[0, 0], [0, 2]]);\n    });\n\n    test('onResize', async () => {\n      await openTerminal(ctx);\n      await timeout(20); // Ensure all init events are fired\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onResize(e => window.calls.push([e.cols, e.rows]));\n      `);\n      await pollFor(ctx.page, `window.calls`, []);\n      await ctx.page.evaluate(`window.term.resize(10, 5)`);\n      await pollFor(ctx.page, `window.calls`, [[10, 5]]);\n      await ctx.page.evaluate(`window.term.resize(20, 15)`);\n      await pollFor(ctx.page, `window.calls`, [[10, 5], [20, 15]]);\n    });\n\n    test('resize during write should not throw', async () => {\n      await openTerminal(ctx, { rows: 50, cols: 80 });\n      const largeData = 'x'.repeat(10000);\n      await ctx.proxy.write(largeData);\n      await ctx.proxy.resize(80, 10);\n      await ctx.proxy.write(largeData);\n      await ctx.proxy.resize(80, 5);\n      await ctx.proxy.write(largeData);\n    });\n\n    test('onTitleChange', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onTitleChange(e => window.calls.push(e));\n      `);\n      await pollFor(ctx.page, `window.calls`, []);\n      await ctx.page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`);\n      await pollFor(ctx.page, `window.calls`, ['foo']);\n    });\n    test('onBell', async () => {\n      await openTerminal(ctx);\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.term.onBell(() => window.calls.push(true));\n      `);\n      await pollFor(ctx.page, `window.calls`, []);\n      await ctx.page.evaluate(`window.term.write('\\x07')`);\n      await pollFor(ctx.page, `window.calls`, [true]);\n    });\n  });\n\n  test.describe('buffer', () => {\n    test('cursorX, cursorY', async () => {\n      await openTerminal(ctx, { rows: 5, cols: 5 });\n      strictEqual(await ctx.proxy.buffer.active.cursorX, 0);\n      strictEqual(await ctx.proxy.buffer.active.cursorY, 0);\n      await ctx.proxy.write('foo');\n      strictEqual(await ctx.proxy.buffer.active.cursorX, 3);\n      strictEqual(await ctx.proxy.buffer.active.cursorY, 0);\n      await ctx.proxy.write('\\n');\n      strictEqual(await ctx.proxy.buffer.active.cursorX, 3);\n      strictEqual(await ctx.proxy.buffer.active.cursorY, 1);\n      await ctx.proxy.write('\\r');\n      strictEqual(await ctx.proxy.buffer.active.cursorX, 0);\n      strictEqual(await ctx.proxy.buffer.active.cursorY, 1);\n      await ctx.proxy.write('abcde');\n      strictEqual(await ctx.proxy.buffer.active.cursorX, 5);\n      strictEqual(await ctx.proxy.buffer.active.cursorY, 1);\n      await ctx.proxy.write('\\n\\r\\n\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.cursorX, 0);\n      strictEqual(await ctx.proxy.buffer.active.cursorY, 4);\n    });\n\n    test('viewportY', async () => {\n      await openTerminal(ctx, { rows: 5 });\n      strictEqual(await ctx.proxy.buffer.active.viewportY, 0);\n      await ctx.proxy.write('\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.viewportY, 0);\n      await ctx.proxy.write('\\n');\n      strictEqual(await ctx.proxy.buffer.active.viewportY, 1);\n      await ctx.proxy.write('\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.viewportY, 5);\n      await ctx.proxy.scrollLines(-1);\n      strictEqual(await ctx.proxy.buffer.active.viewportY, 4);\n      await ctx.proxy.scrollToTop();\n      strictEqual(await ctx.proxy.buffer.active.viewportY, 0);\n    });\n\n    test('baseY', async () => {\n      await openTerminal(ctx, { rows: 5 });\n      strictEqual(await ctx.proxy.buffer.active.baseY, 0);\n      await ctx.proxy.write('\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.baseY, 0);\n      await ctx.proxy.write('\\n');\n      strictEqual(await ctx.proxy.buffer.active.baseY, 1);\n      await ctx.proxy.write('\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.baseY, 5);\n      await ctx.proxy.scrollLines(-1);\n      strictEqual(await ctx.proxy.buffer.active.baseY, 5);\n      await ctx.proxy.scrollToTop();\n      strictEqual(await ctx.proxy.buffer.active.baseY, 5);\n    });\n\n    test('length', async () => {\n      await openTerminal(ctx, { rows: 5 });\n      strictEqual(await ctx.proxy.buffer.active.length, 5);\n      await ctx.proxy.write('\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.length, 5);\n      await ctx.proxy.write('\\n');\n      strictEqual(await ctx.proxy.buffer.active.length, 6);\n      await ctx.proxy.write('\\n\\n\\n\\n');\n      strictEqual(await ctx.proxy.buffer.active.length, 10);\n    });\n\n    test.describe('getLine', () => {\n      test('invalid index', async () => {\n        await openTerminal(ctx, { rows: 5 });\n        strictEqual(await ctx.proxy.buffer.active.getLine(-1), undefined);\n        strictEqual(await ctx.proxy.buffer.active.getLine(5), undefined);\n      });\n\n      test('isWrapped', async () => {\n        await openTerminal(ctx, { cols: 5 });\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.isWrapped, false);\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(1))!.isWrapped, false);\n        await ctx.proxy.write('abcde');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.isWrapped, false);\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(1))!.isWrapped, false);\n        await ctx.proxy.write('f');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.isWrapped, false);\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(1))!.isWrapped, true);\n      });\n\n      test('translateToString', async () => {\n        await openTerminal(ctx, { cols: 5 });\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(), '     ');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(true), '');\n        await ctx.proxy.write('foo');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(), 'foo  ');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(true), 'foo');\n        await ctx.proxy.write('bar');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(), 'fooba');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(true), 'fooba');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(1))!.translateToString(true), 'r');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(false, 1), 'ooba');\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.translateToString(false, 1, 3), 'oo');\n      });\n\n      test('getCell', async () => {\n        await openTerminal(ctx, { cols: 5 });\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.getCell(-1), undefined);\n        strictEqual(await (await ctx.proxy.buffer.active.getLine(0))!.getCell(5), undefined);\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0))!.getChars(), '');\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0))!.getWidth(), 1);\n        await ctx.proxy.write('a文');\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0))!.getChars(), 'a');\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(0))!.getWidth(), 1);\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1))!.getChars(), '文');\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(1))!.getWidth(), 2);\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(2))!.getChars(), '');\n        strictEqual(await (await (await ctx.proxy.buffer.active.getLine(0))!.getCell(2))!.getWidth(), 0);\n      });\n\n      test('clearMarkers', async () => {\n        await openTerminal(ctx, { cols: 5 });\n        await ctx.page.evaluate(`\n          window.disposeStack = [];\n          `);\n        await ctx.proxy.write('\\n\\n\\n\\n');\n        await ctx.proxy.write('\\n\\n\\n\\n');\n        await ctx.proxy.write('\\n\\n\\n\\n');\n        await ctx.proxy.write('\\n\\n\\n\\n');\n        await ctx.page.evaluate(`window.term.registerMarker(1)`);\n        await ctx.page.evaluate(`window.term.registerMarker(2)`);\n        await ctx.page.evaluate(`window.term.scrollLines(10)`);\n        await ctx.page.evaluate(`window.term.registerMarker(3)`);\n        await ctx.page.evaluate(`window.term.registerMarker(4)`);\n        await ctx.page.evaluate(`\n          for (let i = 0; i < window.term.markers.length; ++i) {\n              const marker = window.term.markers[i];\n              marker.onDispose(() => window.disposeStack.push(marker));\n          }`);\n        await ctx.page.evaluate(`window.term.clear()`);\n        strictEqual(await ctx.page.evaluate(`window.disposeStack.length`), 4);\n      });\n    });\n\n    test('active, normal, alternate', async () => {\n      await openTerminal(ctx, { cols: 5 });\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'normal');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.normal.type`), 'normal');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.alternate.type`), 'alternate');\n\n      await ctx.proxy.write('norm ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined);\n\n      await ctx.proxy.write('\\x1b[?47h\\r'); // use alternate screen buffer\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'alternate');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.normal.type`), 'normal');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.alternate.type`), 'alternate');\n\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), '     ');\n      await ctx.proxy.write('alt  ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'alt  ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.alternate.getLine(0).translateToString()`), 'alt  ');\n\n      await ctx.proxy.write('\\x1b[?47l\\r'); // use normal screen buffer\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.type`), 'normal');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.normal.type`), 'normal');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.alternate.type`), 'alternate');\n\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');\n      strictEqual(await ctx.page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined);\n    });\n  });\n\n  test.describe('modes', () => {\n    test.beforeEach(() => openTerminal(ctx));\n    test('defaults', async () => {\n      deepStrictEqual(await ctx.proxy.modes, {\n        applicationCursorKeysMode: false,\n        applicationKeypadMode: false,\n        bracketedPasteMode: false,\n        insertMode: false,\n        mouseTrackingMode: 'none',\n        originMode: false,\n        reverseWraparoundMode: false,\n        sendFocusMode: false,\n        showCursor: true,\n        synchronizedOutputMode: false,\n        win32InputMode: false,\n        wraparoundMode: true\n      });\n    });\n    test('applicationCursorKeysMode', async () => {\n      await ctx.proxy.write('\\x1b[?1h');\n      strictEqual((await ctx.proxy.modes).applicationCursorKeysMode, true);\n      await ctx.proxy.write('\\x1b[?1l');\n      strictEqual((await ctx.proxy.modes).applicationCursorKeysMode, false);\n    });\n    test('applicationKeypadMode', async () => {\n      await ctx.proxy.write('\\x1b[?66h');\n      strictEqual((await ctx.proxy.modes).applicationKeypadMode, true);\n      await ctx.proxy.write('\\x1b[?66l');\n      strictEqual((await ctx.proxy.modes).applicationKeypadMode, false);\n    });\n    test('bracketedPasteMode', async () => {\n      await ctx.proxy.write('\\x1b[?2004h');\n      strictEqual((await ctx.proxy.modes).bracketedPasteMode, true);\n      await ctx.proxy.write('\\x1b[?2004l');\n      strictEqual((await ctx.proxy.modes).bracketedPasteMode, false);\n    });\n    test('insertMode', async () => {\n      await ctx.proxy.write('\\x1b[4h');\n      strictEqual((await ctx.proxy.modes).insertMode, true);\n      await ctx.proxy.write('\\x1b[4l');\n      strictEqual((await ctx.proxy.modes).insertMode, false);\n    });\n    test('mouseTrackingMode', async () => {\n      await ctx.proxy.write('\\x1b[?9h');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'x10');\n      await ctx.proxy.write('\\x1b[?9l');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'none');\n      await ctx.proxy.write('\\x1b[?1000h');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'vt200');\n      await ctx.proxy.write('\\x1b[?1000l');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'none');\n      await ctx.proxy.write('\\x1b[?1002h');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'drag');\n      await ctx.proxy.write('\\x1b[?1002l');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'none');\n      await ctx.proxy.write('\\x1b[?1003h');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'any');\n      await ctx.proxy.write('\\x1b[?1003l');\n      strictEqual((await ctx.proxy.modes).mouseTrackingMode, 'none');\n    });\n    test('originMode', async () => {\n      await ctx.proxy.write('\\x1b[?6h');\n      strictEqual((await ctx.proxy.modes).originMode, true);\n      await ctx.proxy.write('\\x1b[?6l');\n      strictEqual((await ctx.proxy.modes).originMode, false);\n    });\n    test('reverseWraparoundMode', async () => {\n      await ctx.proxy.write('\\x1b[?45h');\n      strictEqual((await ctx.proxy.modes).reverseWraparoundMode, true);\n      await ctx.proxy.write('\\x1b[?45l');\n      strictEqual((await ctx.proxy.modes).reverseWraparoundMode, false);\n    });\n    test('sendFocusMode', async () => {\n      await ctx.proxy.write('\\x1b[?1004h');\n      strictEqual((await ctx.proxy.modes).sendFocusMode, true);\n      await ctx.proxy.write('\\x1b[?1004l');\n      strictEqual((await ctx.proxy.modes).sendFocusMode, false);\n    });\n    test('wraparoundMode', async () => {\n      await ctx.proxy.write('\\x1b[?7h');\n      strictEqual((await ctx.proxy.modes).wraparoundMode, true);\n      await ctx.proxy.write('\\x1b[?7l');\n      strictEqual((await ctx.proxy.modes).wraparoundMode, false);\n    });\n  });\n\n  test('dispose', async () => {\n    await ctx.page.evaluate(`\n      if ('term' in window) {\n        try {\n          window.term.dispose();\n        } catch {}\n      }\n      window.term = new Terminal();\n      window.term.dispose();\n    `);\n    strictEqual(await ctx.page.evaluate(`window.term._core._store._isDisposed`), true);\n  });\n\n  test('dispose (opened)', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      if ('term' in window) {\n        try {\n          window.term.dispose();\n        } catch {}\n      }\n    `);\n    strictEqual(await ctx.page.evaluate(`window.term._core._store._isDisposed`), true);\n  });\n\n  test('render when visible after hidden', async () => {\n    await openTerminal(ctx);\n    await ctx.page.evaluate(`\n      if ('term' in window) {\n        try {\n          window.term.dispose();\n        } catch {}\n      }\n    `);\n    await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);\n    await ctx.page.evaluate(`window.term = new Terminal()`);\n    await ctx.page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);\n    await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);\n    await pollFor(ctx.page, `window.term.dimensions.css.cell.width > 0`, true);\n  });\n\n  test.describe('registerDecoration', () => {\n    test.describe('bufferDecorations', () => {\n      test('should register decorations and render them when terminal open is called', async () => {\n        await openTerminal(ctx);\n        await ctx.page.evaluate(`window.marker1 = window.term.registerMarker(1)`);\n        await ctx.page.evaluate(`window.marker2 = window.term.registerMarker(2)`);\n        await ctx.page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);\n        await ctx.page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);\n        await pollFor(ctx.page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2);\n      });\n      test('should return undefined when the marker has already been disposed of', async () => {\n        await openTerminal(ctx);\n        await ctx.page.evaluate(`window.marker = window.term.registerMarker(1)`);\n        await ctx.page.evaluate(`window.marker.dispose()`);\n        await pollFor(ctx.page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined);\n      });\n      test('should throw when a negative x offset is provided', async () => {\n        await openTerminal(ctx);\n        await ctx.page.evaluate(`window.marker = window.term.registerMarker(1)`);\n        await ctx.page.evaluate(`\n        try {\n          window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });\n        } catch (e) {\n          window.throwMessage = e.message;\n        }\n      `);\n        await pollFor(ctx.page, 'window.throwMessage', 'This API only accepts positive integers');\n      });\n    });\n    test.describe('overviewRulerDecorations', () => {\n      test('should not add an overview ruler when width is not set', async () => {\n        await openTerminal(ctx, { scrollbar: { overviewRuler: {} } });\n        await ctx.page.evaluate(`window.marker1 = window.term.registerMarker(1)`);\n        await ctx.page.evaluate(`window.marker2 = window.term.registerMarker(2)`);\n        await ctx.page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);\n        await ctx.page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);\n        await pollFor(ctx.page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0);\n      });\n      test('should add an overview ruler when width is set', async () => {\n        await openTerminal(ctx, { scrollbar: { width: 15, overviewRuler: {} } });\n        await ctx.page.evaluate(`window.marker1 = window.term.registerMarker(1)`);\n        await ctx.page.evaluate(`window.marker2 = window.term.registerMarker(2)`);\n        await ctx.page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);\n        await ctx.page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);\n        await pollFor(ctx.page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1);\n      });\n    });\n  });\n\n  test.describe('registerLinkProvider', () => {\n    test('should fire provideLinks when hovering cells', async () => {\n      await openTerminal(ctx);\n      // Focus the terminal as the cursor will show and trigger a rerender, which can clear the\n      // active link\n      await ctx.proxy.focus();\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.disposable = window.term.registerLinkProvider({\n          provideLinks: (position, cb) => {\n            calls.push(position);\n            cb(undefined);\n          }\n        });\n      `);\n      const dims = await getDimensions();\n      await moveMouseCell(dims, 1, 1);\n      await moveMouseCell(dims, 2, 2);\n      await moveMouseCell(dims, 10, 4);\n      await pollFor(ctx.page, `window.calls`, [1, 2, 4]);\n      await ctx.page.evaluate(`window.disposable.dispose()`);\n    });\n\n    test('should fire hover and leave events on the link', async () => {\n      await openTerminal(ctx);\n      // Focus the terminal as the cursor will show and trigger a rerender, which can clear the\n      // active link\n      await ctx.page.evaluate('window.term.focus()');\n      await ctx.proxy.write('foo bar baz');\n      // Wait for renderer to catch up as links are cleared on render\n      await pollFor(ctx.page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.disposable = window.term.registerLinkProvider({\n          provideLinks: (position, cb) => {\n            window.calls.push('provide ' + position);\n            if (position === 1) {\n              window.calls.push('match');\n              cb([{\n                range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },\n                text: 'bar',\n                activate: () => window.calls.push('activate'),\n                hover: () => window.calls.push('hover'),\n                leave: () => window.calls.push('leave')\n              }]);\n            }\n          }\n        });\n      `);\n      const dims = await getDimensions();\n      await moveMouseCell(dims, 5, 1);\n      await timeout(100);\n      await moveMouseCell(dims, 4, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'match', 'hover', 'leave' ]);\n      await moveMouseCell(dims, 7, 1);\n      await timeout(100);\n      await moveMouseCell(dims, 8, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', 'hover', 'leave']);\n      await ctx.page.evaluate(`window.disposable.dispose()`);\n    });\n\n    test('should work fine when hover and leave callbacks are not provided', async () => {\n      await openTerminal(ctx);\n      // Focus the terminal as the cursor will show and trigger a rerender, which can clear the\n      // active link\n      await ctx.page.evaluate('window.term.focus()');\n      await ctx.proxy.write('foo bar baz');\n      // Wait for renderer to catch up as links are cleared on render\n      await pollFor(ctx.page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.disposable = window.term.registerLinkProvider({\n          provideLinks: (position, cb) => {\n            window.calls.push('provide ' + position);\n            if (position === 1) {\n              window.calls.push('match 1');\n              cb([{\n                range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },\n                text: 'bar',\n                activate: () => window.calls.push('activate')\n              }]);\n            } else if (position === 2) {\n              window.calls.push('match 2');\n              cb([{\n                range: { start: { x: 5, y: 2 }, end: { x: 7, y: 2 } },\n                text: 'bar',\n                activate: () => window.calls.push('activate')\n              }]);\n            }\n          }\n        });\n      `);\n      const dims = await getDimensions();\n      await moveMouseCell(dims, 5, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'match 1']);\n      await moveMouseCell(dims, 4, 2);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2']);\n      await moveMouseCell(dims, 7, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2', 'provide 1', 'match 1']);\n      await moveMouseCell(dims, 6, 2);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2', 'provide 1', 'match 1', 'provide 2', 'match 2']);\n      await ctx.page.evaluate(`window.disposable.dispose()`);\n    });\n\n    test('should fire activate events when clicking the link', async () => {\n      await openTerminal(ctx);\n      // Focus the terminal as the cursor will show and trigger a rerender, which can clear the\n      // active link\n      await ctx.page.evaluate('window.term.focus()');\n      await ctx.proxy.write('a b c');\n      // Wait for renderer to catch up as links are cleared on render\n      await pollFor(ctx.page, `document.querySelector('.xterm-rows').textContent`, 'a b c ');\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.disposable = window.term.registerLinkProvider({\n          provideLinks: (y, cb) => {\n            window.calls.push('provide ' + y);\n            cb([{\n              range: { start: { x: 1, y }, end: { x: 80, y } },\n              text: window.term.buffer.active.getLine(y - 1).translateToString(),\n              activate: (_, text) => window.calls.push('activate ' + y),\n              hover: () => window.calls.push('hover ' + y),\n              leave: () => window.calls.push('leave ' + y)\n            }]);\n          }\n        });\n      `);\n      const dims = await getDimensions();\n      await moveMouseCell(dims, 3, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1']);\n      await ctx.page.mouse.down();\n      await ctx.page.mouse.up();\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1', 'activate 1']);\n      await moveMouseCell(dims, 1, 2);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2']);\n      await ctx.page.mouse.down();\n      await ctx.page.mouse.up();\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2']);\n      await moveMouseCell(dims, 5, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2', 'leave 2', 'provide 1', 'hover 1']);\n      await ctx.page.mouse.down();\n      await ctx.page.mouse.up();\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2', 'leave 2', 'provide 1', 'hover 1', 'activate 1']);\n      await ctx.page.evaluate(`window.disposable.dispose()`);\n    });\n\n    test('should work when multiple links are provided on the same line', async () => {\n      await openTerminal(ctx);\n      // Focus the terminal as the cursor will show and trigger a rerender, which can clear the\n      // active link\n      await ctx.page.evaluate('window.term.focus()');\n      await ctx.proxy.write('foo bar baz');\n      // Wait for renderer to catch up as links are cleared on render\n      await pollFor(ctx.page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.disposable = window.term.registerLinkProvider({\n          provideLinks: (position, cb) => {\n            window.calls.push('provide ' + position);\n            if (position === 1) {\n              cb([{\n                range: { start: { x: 1, y: 1 }, end: { x: 3, y: 1 } },\n                text: '',\n                activate: () => window.calls.push('activate'),\n                hover: () => window.calls.push('hover 1-3'),\n                leave: () => window.calls.push('leave 1-3')\n              }, {\n                range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },\n                text: '',\n                activate: () => window.calls.push('activate'),\n                hover: () => window.calls.push('hover 5-7'),\n                leave: () => window.calls.push('leave 5-7')\n              }, {\n                range: { start: { x: 9, y: 1 }, end: { x: 11, y: 1 } },\n                text: '',\n                activate: () => window.calls.push('activate'),\n                hover: () => window.calls.push('hover 9-11'),\n                leave: () => window.calls.push('leave 9-11')\n              }]);\n            }\n          }\n        });\n      `);\n      const dims = await getDimensions();\n      await moveMouseCell(dims, 2, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3']);\n      await moveMouseCell(dims, 6, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7']);\n      await moveMouseCell(dims, 6, 2);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2']);\n      await moveMouseCell(dims, 10, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2', 'provide 1', 'hover 9-11']);\n      await ctx.page.evaluate(`window.disposable.dispose()`);\n    });\n\n    test('should dispose links when hovering away', async () => {\n      await openTerminal(ctx);\n      // Focus the terminal as the cursor will show and trigger a rerender, which can clear the\n      // active link\n      await ctx.page.evaluate('window.term.focus()');\n      await ctx.proxy.write('foo bar baz');\n      // Wait for renderer to catch up as links are cleared on render\n      await pollFor(ctx.page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');\n      await ctx.page.evaluate(`\n        window.calls = [];\n        window.disposable = window.term.registerLinkProvider({\n          provideLinks: (position, cb) => {\n            window.calls.push('provide ' + position);\n            if (position === 1) {\n              cb([{\n                range: { start: { x: 1, y: 1 }, end: { x: 3, y: 1 } },\n                text: '',\n                activate: () => window.calls.push('activate'),\n                dispose: () => window.calls.push('dispose 1-3'),\n                hover: () => window.calls.push('hover 1-3'),\n                leave: () => window.calls.push('leave 1-3')\n              }, {\n                range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },\n                text: '',\n                activate: () => window.calls.push('activate'),\n                dispose: () => window.calls.push('dispose 5-7'),\n                hover: () => window.calls.push('hover 5-7'),\n                leave: () => window.calls.push('leave 5-7')\n              }, {\n                range: { start: { x: 9, y: 1 }, end: { x: 11, y: 1 } },\n                text: '',\n                activate: () => window.calls.push('activate'),\n                dispose: () => window.calls.push('dispose 9-11'),\n                hover: () => window.calls.push('hover 9-11'),\n                leave: () => window.calls.push('leave 9-11')\n              }]);\n            }\n          }\n        });\n      `);\n      const dims = await getDimensions();\n      await moveMouseCell(dims, 2, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3']);\n      await moveMouseCell(dims, 6, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7']);\n      await moveMouseCell(dims, 6, 2);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2']);\n      await moveMouseCell(dims, 10, 1);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2', 'provide 1', 'hover 9-11']);\n      await moveMouseCell(dims, 10, 2);\n      await pollFor(ctx.page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2', 'provide 1', 'hover 9-11', 'leave 9-11', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2']);\n      await ctx.page.evaluate(`window.disposable.dispose()`);\n    });\n  });\n});\n\ninterface IDimensions {\n  top: number;\n  left: number;\n  renderDimensions: IRenderDimensions;\n}\n\nasync function getDimensions(): Promise<IDimensions> {\n  return await ctx.page.evaluate(`\n    (function() {\n      const rect = document.querySelector('.xterm-rows').getBoundingClientRect();\n      return {\n        top: rect.top,\n        left: rect.left,\n        renderDimensions: window.term.dimensions\n      };\n    })();\n  `);\n}\n\nasync function getCellCoordinates(dimensions: IDimensions, col: number, row: number): Promise<{ x: number, y: number }> {\n  return {\n    x: dimensions.left + dimensions.renderDimensions.device.cell.width * (col - 0.5),\n    y: dimensions.top + dimensions.renderDimensions.device.cell.height * (row - 0.5)\n  };\n}\n\nasync function moveMouseCell(dimensions: IDimensions, col: number, row: number): Promise<void> {\n  const coords = await getCellCoordinates(dimensions, col, row);\n  await ctx.page.mouse.move(coords.x, coords.y);\n}\n"
  },
  {
    "path": "test/playwright/TestUtils.ts",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Browser, JSHandle, Page } from '@playwright/test';\nimport { deepStrictEqual, strictEqual } from 'assert';\nimport type { IRenderDimensions as IRenderDimensionsInternal } from 'browser/renderer/shared/Types';\nimport type { IRenderService } from 'browser/services/Services';\nimport type { ICoreTerminal, IDisposable, IMarker } from 'common/Types';\nimport * as playwright from '@playwright/test';\nimport { IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, IRenderDimensions, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from '@xterm/xterm';\n\ntype PageFunction<Arg, R> = (arg: Arg) => R | Promise<R>;\n\nexport interface ITestContext {\n  browser: Browser;\n  page: Page;\n  termHandle: JSHandle<Terminal>;\n  proxy: TerminalProxy;\n}\n\nexport async function createTestContext(browser: Browser): Promise<ITestContext> {\n  const page = await browser.newPage();\n  page.on('console', e => console.log(`[${browser.browserType().name()}:${e.type()}]`, e));\n  page.on('pageerror', e => console.error(`[${browser.browserType().name()}]`, e));\n  await page.goto('/test');\n  const proxy = new TerminalProxy(page);\n  proxy.initPage();\n  return {\n    browser,\n    page,\n    termHandle: await page.evaluateHandle('window.term'),\n    proxy\n  };\n}\n\ninterface IListener<T, U = void> {\n  (arg1: T, arg2: U): void;\n}\nexport interface IEvent<T, U = void> {\n  (listener: (arg1: T, arg2: U) => any): IDisposable;\n}\nclass EventEmitter<T, U = void> {\n  private _listeners: Set<IListener<T, U>> = new Set();\n  private _event?: IEvent<T, U>;\n  private _disposed: boolean = false;\n\n  public get event(): IEvent<T, U> {\n    this._event ??= (listener: (arg1: T, arg2: U) => any) => {\n      this._listeners.add(listener);\n      const disposable = {\n        dispose: () => {\n          if (!this._disposed) {\n            this._listeners.delete(listener);\n          }\n        }\n      };\n      return disposable;\n    };\n    return this._event;\n  }\n\n  public fire(arg1: T, arg2: U): void {\n    const queue: IListener<T, U>[] = [];\n    for (const l of this._listeners.values()) {\n      queue.push(l);\n    }\n    for (let i = 0; i < queue.length; i++) {\n      queue[i].call(undefined, arg1, arg2);\n    }\n  }\n\n  public dispose(): void {\n    this.clearListeners();\n    this._disposed = true;\n  }\n\n  public clearListeners(): void {\n    if (this._listeners) {\n      this._listeners.clear();\n    }\n  }\n}\n\ntype EnsureAsync<T> = T extends PromiseLike<any> ? T : Promise<T>;\ntype EnsureAsyncProperties<T> = {\n  [Key in keyof T]: EnsureAsync<T[Key]>\n};\ntype EnsureAsyncMethods<T> = {\n  [K in keyof T]: T[K] extends (...args: infer Args) => infer Return\n    ? (...args: Args) => Promise<Return>\n    : T[K];\n};\n\n/**\n * A type that proxy objects implement that enables overriding a base interface with a set of types\n * to override as async (ie. properties that are not synced to the node side), and a set of types to\n * omit from the interface (ie. properties that map to completely different implementations).\n */\ntype PlaywrightApiProxy<TBaseInterface, TAsyncPropOverrides extends keyof TBaseInterface, TAsyncMethodOverrides extends keyof TBaseInterface, TCustomOverrides extends keyof TBaseInterface> = (\n  // Interfaces that the proxy implements as async\n  EnsureAsyncProperties<Pick<TBaseInterface, TAsyncPropOverrides>> &\n  EnsureAsyncMethods<Pick<TBaseInterface, TAsyncMethodOverrides>> &\n  // Interfaces that the proxy implements as is (exclude async/custom)\n  Omit<TBaseInterface, TAsyncPropOverrides | TAsyncMethodOverrides | TCustomOverrides>\n);\n\ninterface ITerminalProxyCustomMethods {\n  evaluate<T>(pageFunction: PageFunction<Terminal[], T>): Promise<T>;\n  write(data: string | Uint8Array): Promise<void>;\n}\n\ntype TerminalProxyAsyncPropOverrides = 'cols' | 'rows' | 'modes';\ntype TerminalProxyAsyncMethodOverrides = 'hasSelection' | 'getSelection' | 'getSelectionPosition' | 'registerMarker' | 'registerDecoration';\ntype TerminalProxyCustomOverrides = 'buffer' | 'dimensions' | (\n  // The below are not implemented yet\n  'element' |\n  'screenElement' |\n  'textarea' |\n  'markers' |\n  'unicode' |\n  'parser' |\n  'options' |\n  'open' |\n  'attachCustomKeyEventHandler' |\n  'attachCustomWheelEventHandler' |\n  'registerLinkProvider' |\n  'registerCharacterJoiner' |\n  'deregisterCharacterJoiner' |\n  'loadAddon'\n);\nexport class TerminalProxy implements ITerminalProxyCustomMethods, PlaywrightApiProxy<Terminal, TerminalProxyAsyncPropOverrides, TerminalProxyAsyncMethodOverrides, TerminalProxyCustomOverrides> {\n  constructor(private readonly _page: Page) {\n  }\n\n  /**\n   * Initialize the proxy for a new playwright page.\n   */\n  public async initPage(): Promise<void> {\n    await this._page.exposeFunction('onBell', () => this._onBell.fire());\n    await this._page.exposeFunction('onBinary', (e: string) => this._onBinary.fire(e));\n    await this._page.exposeFunction('onCursorMove', () => this._onCursorMove.fire());\n    await this._page.exposeFunction('onData', (e: string) => this._onData.fire(e));\n    await this._page.exposeFunction('onKey', (e: { key: string, domEvent: KeyboardEvent }) => this._onKey.fire(e));\n    await this._page.exposeFunction('onLineFeed', () => this._onLineFeed.fire());\n    await this._page.exposeFunction('onRender', (e: { start: number, end: number }) => this._onRender.fire(e));\n    await this._page.exposeFunction('onResize', (e: { cols: number, rows: number }) => this._onResize.fire(e));\n    await this._page.exposeFunction('onScroll', (e: number) => this._onScroll.fire(e));\n    await this._page.exposeFunction('onSelectionChange', () => this._onSelectionChange.fire());\n    await this._page.exposeFunction('onTitleChange', (e: string) => this._onTitleChange.fire(e));\n    await this._page.exposeFunction('onWriteParsed', () => this._onWriteParsed.fire());\n    await this._page.exposeFunction('onDimensionsChange', (e: IRenderDimensions) => this._onDimensionsChange.fire(e));\n  }\n\n  /**\n   * Initialize the proxy for a new terminal object.\n   */\n  public async initTerm(): Promise<void> {\n    this._onBell.dispose();\n    this._onBinary.dispose();\n    this._onCursorMove.dispose();\n    this._onData.dispose();\n    this._onKey.dispose();\n    this._onLineFeed.dispose();\n    this._onRender.dispose();\n    this._onResize.dispose();\n    this._onScroll.dispose();\n    this._onSelectionChange.dispose();\n    this._onTitleChange.dispose();\n    this._onWriteParsed.dispose();\n    this._onDimensionsChange.dispose();\n\n    this._onBell = new EventEmitter();\n    this._onBinary = new EventEmitter();\n    this._onCursorMove = new EventEmitter();\n    this._onData = new EventEmitter();\n    this._onKey = new EventEmitter();\n    this._onLineFeed = new EventEmitter();\n    this._onRender = new EventEmitter();\n    this._onResize = new EventEmitter();\n    this._onScroll = new EventEmitter();\n    this._onSelectionChange = new EventEmitter();\n    this._onTitleChange = new EventEmitter();\n    this._onWriteParsed = new EventEmitter();\n    this._onDimensionsChange = new EventEmitter();\n\n    await this.evaluate(([term]) => term.onBell((window as any).onBell));\n    await this.evaluate(([term]) => term.onBinary((window as any).onBinary));\n    await this.evaluate(([term]) => term.onCursorMove((window as any).onCursorMove));\n    await this.evaluate(([term]) => term.onData((window as any).onData));\n    await this.evaluate(([term]) => term.onKey((window as any).onKey));\n    await this.evaluate(([term]) => term.onLineFeed((window as any).onLineFeed));\n    await this.evaluate(([term]) => term.onRender((window as any).onRender));\n    await this.evaluate(([term]) => term.onResize((window as any).onResize));\n    await this.evaluate(([term]) => term.onScroll((window as any).onScroll));\n    await this.evaluate(([term]) => term.onSelectionChange((window as any).onSelectionChange));\n    await this.evaluate(([term]) => term.onTitleChange((window as any).onTitleChange));\n    await this.evaluate(([term]) => term.onWriteParsed((window as any).onWriteParsed));\n    await this.evaluate(([term]) => term.onDimensionsChange((window as any).onDimensionsChange));\n  }\n\n  // #region Events\n  private _onBell = new EventEmitter<void>();\n  public get onBell(): IEvent<void> { return this._onBell.event; }\n  private _onBinary = new EventEmitter<string>();\n  public get onBinary(): IEvent<string> { return this._onBinary.event; }\n  private _onCursorMove = new EventEmitter<void>();\n  public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }\n  private _onData = new EventEmitter<string>();\n  public get onData(): IEvent<string> { return this._onData.event; }\n  private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();\n  public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }\n  private _onLineFeed = new EventEmitter<void>();\n  public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }\n  private _onRender = new EventEmitter<{ start: number, end: number }>();\n  public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }\n  private _onResize = new EventEmitter<{ cols: number, rows: number }>();\n  public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }\n  private _onScroll = new EventEmitter<number>();\n  public get onScroll(): IEvent<number> { return this._onScroll.event; }\n  private _onDimensionsChange = new EventEmitter<IRenderDimensions>();\n  public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }\n  private _onSelectionChange = new EventEmitter<void>();\n  public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }\n  private _onTitleChange = new EventEmitter<string>();\n  public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }\n  private _onWriteParsed = new EventEmitter<void>();\n  public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }\n  // #endregion\n\n  // #region Simple properties\n  public get cols(): Promise<number> { return this.evaluate(([term]) => term.cols); }\n  public get rows(): Promise<number> { return this.evaluate(([term]) => term.rows); }\n  public get modes(): Promise<IModes> { return this.evaluate(([term]) => term.modes); }\n  public get dimensions(): Promise<IRenderDimensions | undefined> { return this.evaluate(([term]) => term.dimensions); }\n  // #endregion\n\n  // #region Complex properties\n  public get buffer(): TerminalBufferNamespaceProxy { return new TerminalBufferNamespaceProxy(this._page, this); }\n  /**\n   * Exposes somewhat unsafe access to internals for testing things difficult to do with the regular\n   * API\n   */\n  public get core(): TerminalCoreProxy { return new TerminalCoreProxy(this._page, this); }\n  // #endregion\n\n  // #region Proxied methods\n  public async dispose(): Promise<void> { return this.evaluate(([term]) => term.dispose()); }\n  public async reset(): Promise<void> { return this.evaluate(([term]) => term.reset()); }\n  public async clear(): Promise<void> { return this.evaluate(([term]) => term.clear()); }\n  public async focus(): Promise<void> { return this.evaluate(([term]) => term.focus()); }\n  public async blur(): Promise<void> { return this.evaluate(([term]) => term.blur()); }\n  public async hasSelection(): Promise<boolean> { return this.evaluate(([term]) => term.hasSelection()); }\n  public async getSelection(): Promise<string> { return this.evaluate(([term]) => term.getSelection()); }\n  public async getSelectionPosition(): Promise<IBufferRange | undefined> { return this.evaluate(([term]) => term.getSelectionPosition()); }\n  public async selectAll(): Promise<void> { return this.evaluate(([term]) => term.selectAll()); }\n  public async selectLines(start: number, end: number): Promise<void> { return this._page.evaluate(([term, start, end]) => term.selectLines(start, end), [await this.getHandle(), start, end] as const); }\n  public async clearSelection(): Promise<void> { return this.evaluate(([term]) => term.clearSelection()); }\n  public async select(column: number, row: number, length: number): Promise<void> { return this._page.evaluate(([term, column, row, length]) => term.select(column, row, length), [await this.getHandle(), column, row, length] as const); }\n  public async paste(data: string): Promise<void> { return this._page.evaluate(([term, data]) => term.paste(data), [await this.getHandle(), data] as const); }\n  public async refresh(start: number, end: number): Promise<void> { return this._page.evaluate(([term, start, end]) => term.refresh(start, end), [await this.getHandle(), start, end] as const); }\n  public async getOption<T extends keyof ITerminalOptions>(key: T): Promise<ITerminalOptions[T]> { return this._page.evaluate(([term, key]) => term.options[key as T], [await this.getHandle(), key] as const); }\n  public async setOption<T extends keyof ITerminalOptions>(key: T, value: ITerminalOptions[T]): Promise<any> { return this._page.evaluate(([term, key, value]) => term.options[key as T] = (value as ITerminalOptions[T]), [await this.getHandle(), key, value] as const); }\n  public async setOptions(value: Partial<ITerminalOptions>): Promise<any> {\n    return this._page.evaluate(([term, value]) => {\n      term.options = value;\n    }, [await this.getHandle(), value] as const);\n  }\n  public async scrollToTop(): Promise<void> { return this.evaluate(([term]) => term.scrollToTop()); }\n  public async scrollToBottom(): Promise<void> { return this.evaluate(([term]) => term.scrollToBottom()); }\n  public async scrollPages(pageCount: number): Promise<void> { return this._page.evaluate(([term, pageCount]) => term.scrollPages(pageCount), [await this.getHandle(), pageCount] as const); }\n  public async scrollToLine(line: number): Promise<void> { return this._page.evaluate(([term, line]) => term.scrollToLine(line), [await this.getHandle(), line] as const); }\n  public async scrollLines(amount: number): Promise<void> { return this._page.evaluate(([term, amount]) => term.scrollLines(amount), [await this.getHandle(), amount] as const); }\n  public async write(data: string | Uint8Array): Promise<void> {\n    return this._page.evaluate(([term, data]) => {\n      return new Promise(r => term.write(typeof data === 'string' ? data : new Uint8Array(data), r));\n    }, [await this.getHandle(), typeof data === 'string' ? data : Array.from(data)] as const);\n  }\n  public async writeln(data: string | Uint8Array): Promise<void> {\n    return this._page.evaluate(([term, data]) => {\n      return new Promise(r => term.writeln(typeof data === 'string' ? data : new Uint8Array(data), r));\n    }, [await this.getHandle(), typeof data === 'string' ? data : Array.from(data)] as const);\n  }\n  public async input(data: string, wasUserInput: boolean = true): Promise<void> { return this.evaluate(([term]) => term.input(data, wasUserInput)); }\n  public async resize(cols: number, rows: number): Promise<void> { return this._page.evaluate(([term, cols, rows]) => term.resize(cols, rows), [await this.getHandle(), cols, rows] as const); }\n  public async registerMarker(y?: number | undefined): Promise<IMarker> { return this._page.evaluate(([term, y]) => term.registerMarker(y), [await this.getHandle(), y] as const); }\n  public async registerDecoration(decorationOptions: IDecorationOptions): Promise<IDecoration | undefined> { return this._page.evaluate(([term, decorationOptions]) => term.registerDecoration(decorationOptions), [await this.getHandle(), decorationOptions] as const); }\n  public async clearTextureAtlas(): Promise<void> { return this.evaluate(([term]) => term.clearTextureAtlas()); }\n  // #endregion\n\n  public async evaluate<T>(pageFunction: PageFunction<Terminal[], T>): Promise<T> {\n    return this._page.evaluate(pageFunction, [await this.getHandle()]);\n  }\n\n  public async evaluateHandle<T>(pageFunction: PageFunction<Terminal[], T>): Promise<JSHandle<T>> {\n    return this._page.evaluateHandle(pageFunction, [await this.getHandle()]);\n  }\n\n  public async getHandle(): Promise<JSHandle<Terminal>> {\n    // This is async because it must be evaluated each time it is called since term may have changed\n    return this._page.evaluateHandle('window.term');\n  }\n}\n\nclass TerminalBufferNamespaceProxy implements PlaywrightApiProxy<IBufferNamespace, never, never, 'active' | 'normal' | 'alternate'> {\n  private _onBufferChange = new EventEmitter<IBuffer>();\n  public readonly onBufferChange = this._onBufferChange.event;\n\n  constructor(\n    private readonly _page: Page,\n    private readonly _proxy: TerminalProxy\n  ) {\n\n  }\n\n  public get active(): TerminalBufferProxy { return new TerminalBufferProxy(this._page, this._proxy, this._proxy.evaluateHandle(([term]) => term.buffer.active)); }\n  public get normal(): TerminalBufferProxy { return new TerminalBufferProxy(this._page, this._proxy, this._proxy.evaluateHandle(([term]) => term.buffer.normal)); }\n  public get alternate(): TerminalBufferProxy { return new TerminalBufferProxy(this._page, this._proxy, this._proxy.evaluateHandle(([term]) => term.buffer.alternate)); }\n}\n\n// TODO: Adopt PlaywrightApiProxy\nclass TerminalBufferProxy /* implements EnsureAsyncProperties<IBuffer>*/ {\n  constructor(\n    private readonly _page: Page,\n    private readonly _proxy: TerminalProxy,\n    private readonly _handle: Promise<JSHandle<IBuffer>>\n  ) {\n  }\n\n  public get type(): Promise<'normal' | 'alternate'> { return this.evaluate(([buffer]) => buffer.type); }\n  public get cursorY(): Promise<number> { return this.evaluate(([buffer]) => buffer.cursorY); }\n  public get cursorX(): Promise<number> { return this.evaluate(([buffer]) => buffer.cursorX); }\n  public get viewportY(): Promise<number> { return this.evaluate(([buffer]) => buffer.viewportY); }\n  public get baseY(): Promise<number> { return this.evaluate(([buffer]) => buffer.baseY); }\n  public get length(): Promise<number> { return this.evaluate(([buffer]) => buffer.length); }\n  public async getLine(y: number): Promise<TerminalBufferLine | undefined> {\n    const lineHandle = await this._page.evaluateHandle(([buffer, y]) => buffer.getLine(y), [await this._handle, y] as const);\n    const value = await lineHandle.jsonValue();\n    if (value) {\n      return new TerminalBufferLine(this._page, lineHandle as JSHandle<IBufferLine>);\n    }\n    return undefined;\n  }\n\n  public async evaluate<T>(pageFunction: PageFunction<IBuffer[], T>): Promise<T> {\n    return this._page.evaluate(pageFunction, [await this._handle]);\n  }\n}\n\n// TODO: Adopt PlaywrightApiProxy\nclass TerminalBufferLine {\n  constructor(\n    private readonly _page: Page,\n    private readonly _handle: JSHandle<IBufferLine>\n  ) {\n  }\n\n  public get length(): Promise<number> { return this.evaluate(([bufferLine]) => bufferLine.length); }\n  public get isWrapped(): Promise<boolean> { return this.evaluate(([bufferLine]) => bufferLine.isWrapped); }\n\n  public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): Promise<string> {\n    return this._page.evaluate(([bufferLine, trimRight, startColumn, endColumn]) => {\n      return bufferLine.translateToString(trimRight, startColumn, endColumn);\n    }, [this._handle, trimRight, startColumn, endColumn] as const);\n  }\n\n  public async getCell(x: number): Promise<TerminalBufferCell | undefined> {\n    const cellHandle = await this._page.evaluateHandle(([bufferLine, x]) => bufferLine.getCell(x), [this._handle, x] as const);\n    const value = await cellHandle.jsonValue();\n    if (value) {\n      return new TerminalBufferCell(this._page, cellHandle as JSHandle<IBufferCell>);\n    }\n    return undefined;\n  }\n\n  public async evaluate<T>(pageFunction: PageFunction<IBufferLine[], T>): Promise<T> {\n    return this._page.evaluate(pageFunction, [this._handle]);\n  }\n}\n\n// TODO: Adopt PlaywrightApiProxy\nclass TerminalBufferCell {\n  constructor(\n    private readonly _page: Page,\n    private readonly _handle: JSHandle<IBufferCell>\n  ) {\n  }\n\n  public getWidth(): Promise<number> { return this.evaluate(([cell]) => cell.getWidth()); }\n  public getChars(): Promise<string> { return this.evaluate(([cell]) => cell.getChars()); }\n  public getCode(): Promise<number> { return this.evaluate(([cell]) => cell.getCode()); }\n\n  public getFgColorMode(): Promise<number> { return this.evaluate(([cell]) => cell.getFgColorMode()); }\n  public getBgColorMode(): Promise<number> { return this.evaluate(([cell]) => cell.getBgColorMode()); }\n  public getFgColor(): Promise<number> { return this.evaluate(([cell]) => cell.getFgColor()); }\n  public getBgColor(): Promise<number> { return this.evaluate(([cell]) => cell.getBgColor()); }\n\n  public isBold(): Promise<number> { return this.evaluate(([cell]) => cell.isBold()); }\n  public isItalic(): Promise<number> { return this.evaluate(([cell]) => cell.isItalic()); }\n  public isDim(): Promise<number> { return this.evaluate(([cell]) => cell.isDim()); }\n  public isUnderline(): Promise<number> { return this.evaluate(([cell]) => cell.isUnderline()); }\n  public isBlink(): Promise<number> { return this.evaluate(([cell]) => cell.isBlink()); }\n  public isInverse(): Promise<number> { return this.evaluate(([cell]) => cell.isInverse()); }\n  public isInvisible(): Promise<number> { return this.evaluate(([cell]) => cell.isInvisible()); }\n  public isStrikethrough(): Promise<number> { return this.evaluate(([cell]) => cell.isStrikethrough()); }\n  public isOverline(): Promise<number> { return this.evaluate(([cell]) => cell.isOverline()); }\n\n  public isFgRGB(): Promise<boolean> { return this.evaluate(([cell]) => cell.isFgRGB()); }\n  public isBgRGB(): Promise<boolean> { return this.evaluate(([cell]) => cell.isBgRGB()); }\n  public isFgPalette(): Promise<boolean> { return this.evaluate(([cell]) => cell.isFgPalette()); }\n  public isBgPalette(): Promise<boolean> { return this.evaluate(([cell]) => cell.isBgPalette()); }\n  public isFgDefault(): Promise<boolean> { return this.evaluate(([cell]) => cell.isFgDefault()); }\n  public isBgDefault(): Promise<boolean> { return this.evaluate(([cell]) => cell.isBgDefault()); }\n\n  public isAttributeDefault(): Promise<boolean> { return this.evaluate(([cell]) => cell.isAttributeDefault()); }\n\n  public async evaluate<T>(pageFunction: PageFunction<IBufferCell[], T>): Promise<T> {\n    return this._page.evaluate(pageFunction, [this._handle]);\n  }\n}\n\nclass TerminalCoreProxy {\n  constructor(\n    private readonly _page: Page,\n    private readonly _proxy: TerminalProxy\n  ) {\n  }\n\n  public get isDisposed(): Promise<boolean> { return this.evaluate(([core]) => (core as any)._isDisposed); }\n  public get renderDimensions(): Promise<IRenderDimensionsInternal> { return this.evaluate(([core]) => ((core as any)._renderService as IRenderService).dimensions); }\n\n  public async triggerBinaryEvent(data: string): Promise<void> {\n    return this._page.evaluate(([core, data]) => core.coreService.triggerBinaryEvent(data), [await this._getCoreHandle(), data] as const);\n  }\n\n\n  private async _getCoreHandle(): Promise<JSHandle<ICoreTerminal>> {\n    return this._proxy.evaluateHandle(([term]) => (term as any)._core as ICoreTerminal);\n  }\n\n  public async evaluate<T>(pageFunction: PageFunction<ICoreTerminal[], T>): Promise<T> {\n    return this._page.evaluate(pageFunction, [await this._getCoreHandle()]);\n  }\n}\n\nexport async function openTerminal(\n  ctx: ITestContext,\n  options: ITerminalOptions | ITerminalInitOnlyOptions = {},\n  testOptions: { useShadowDom?: boolean, loadUnicodeGraphemesAddon?: boolean } = {}\n): Promise<void> {\n  testOptions.useShadowDom ??= false;\n  testOptions.loadUnicodeGraphemesAddon ??= true;\n\n  await ctx.page.evaluate(`\n  if ('term' in window) {\n    try {\n      window.term.dispose();\n    } catch {}\n  }\n  `);\n  // HACK: Tests may have side effects that could cause the terminal not to be removed. This\n  //       assertion catches this case early.\n  strictEqual(await ctx.page.evaluate(`document.querySelector('#terminal-container').children.length`), 0, 'there must be no terminals on the page');\n\n  let script = `\n    window.term = new window.Terminal(${JSON.stringify({ allowProposedApi: true, ...options })});\n    let element = document.querySelector('#terminal-container');\n\n    // Remove shadow root if it exists\n    const newElement = element.cloneNode(false);\n    element.replaceWith(newElement);\n    element = newElement\n`;\n\n\n  if (testOptions.useShadowDom) {\n    script += `\n    const shadowRoot = element.attachShadow({ mode: \"open\" });\n\n    // Copy parent styles to shadow DOM\n    const styles = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]'));\n    styles.forEach((styleEl) => {\n      const clone = document.createElement('link');\n      clone.rel = 'stylesheet';\n      clone.href = styleEl.href;\n      shadowRoot.appendChild(clone);\n    });\n\n    // Create new element inside the shadow DOM\n    element = document.createElement('div');\n    element.style.width = '100%';\n    element.style.height = '100%';\n    shadowRoot.appendChild(element);\n    `;\n  }\n\n  script += `\n    window.term.open(element);\n  `;\n\n  await ctx.page.evaluate(script);\n\n  // HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have\n  // more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453\n  if (testOptions.loadUnicodeGraphemesAddon) {\n    await ctx.page.evaluate(`\n      window.unicode = new UnicodeGraphemesAddon();\n      window.term.loadAddon(window.unicode);\n      window.term.unicode.activeVersion = '15-graphemes';\n    `);\n  }\n  await ctx.page.waitForSelector('.xterm-rows');\n  ctx.termHandle = await ctx.page.evaluateHandle('window.term');\n  await ctx.proxy.initTerm();\n}\n\n\nexport type MaybeAsync<T> = Promise<T> | T;\n\ninterface IPollForOptions<T> {\n  equalityFn?: (a: T, b: T) => boolean;\n  maxDuration?: number;\n  stack?: string;\n}\n\nexport async function pollFor<T>(page: playwright.Page, evalOrFn: string | (() => MaybeAsync<T>), val: T, preFn?: () => Promise<void>, options?: IPollForOptions<T>): Promise<void> {\n  options ??= {};\n  options.stack ??= new Error().stack;\n  if (preFn) {\n    await preFn();\n  }\n  const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();\n\n  if (process.env.DEBUG) {\n    console.log('pollFor\\n  actual: ', JSON.stringify(result), '  expected: ', JSON.stringify(val));\n  }\n\n  let equalityCheck: boolean;\n  if (options.equalityFn) {\n    equalityCheck = options.equalityFn(result as T, val);\n  } else {\n    equalityCheck = true;\n    try {\n      deepStrictEqual(result, val);\n    } catch {\n      equalityCheck = false;\n    }\n  }\n\n  if (!equalityCheck) {\n    options.maxDuration ??= 2000;\n    if (options.maxDuration <= 0) {\n      deepStrictEqual(result, val, ([\n        `pollFor max duration exceeded.`,\n        (`Last comparison: ` +\n          `${typeof result === 'object' ? JSON.stringify(result) : result} (actual) !== ` +\n          `${typeof val === 'object' ? JSON.stringify(val) : val} (expected)`),\n        `Stack: ${options.stack}`\n      ].join('\\n')));\n    }\n    return new Promise<void>(r => {\n      setTimeout(() => r(pollFor(page, evalOrFn, val, preFn, {\n        ...options,\n        maxDuration: options!.maxDuration! - 10,\n        stack: options!.stack\n      })), 10);\n    });\n  }\n}\n\nexport async function pollForApproximate<T>(page: playwright.Page, marginOfError: number, evalOrFn: string | (() => MaybeAsync<T>), val: T, preFn?: () => Promise<void>, maxDuration?: number, stack?: string): Promise<void> {\n  await pollFor(page, evalOrFn, val, preFn, {\n    maxDuration,\n    stack,\n    equalityFn: (a, b) => {\n      if (a === b) {\n        return true;\n      }\n      if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {\n        let success = true;\n        for (let i = 0 ; i < a.length; i++) {\n          if (Math.abs(a[i] - b[i]) > marginOfError) {\n            success = false;\n            break;\n          }\n        }\n        if (success) {\n          return true;\n        }\n      }\n      return false;\n    }\n  });\n}\n\nexport async function writeSync(page: playwright.Page, data: string): Promise<void> {\n  await page.evaluate(`\n    window.ready = false;\n    window.term.write('${data}', () => window.ready = true);\n  `);\n  await pollFor(page, 'window.ready', true);\n}\n\nexport async function timeout(ms: number): Promise<void> {\n  return new Promise<void>(r => setTimeout(r, ms));\n}\n\nexport function getBrowserType(): playwright.BrowserType<playwright.WebKitBrowser> | playwright.BrowserType<playwright.ChromiumBrowser> | playwright.BrowserType<playwright.FirefoxBrowser> {\n  // Default to chromium\n  let browserType: playwright.BrowserType<playwright.WebKitBrowser> | playwright.BrowserType<playwright.ChromiumBrowser> | playwright.BrowserType<playwright.FirefoxBrowser> = playwright['chromium'];\n\n  const index = process.argv.indexOf('--browser');\n  if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {\n    const string = process.argv[index + 1];\n    if (string === 'firefox' || string === 'webkit') {\n      browserType = playwright[string];\n    }\n  }\n\n  return browserType;\n}\n\nexport function launchBrowser(opts?: playwright.LaunchOptions): Promise<playwright.Browser> {\n  const browserType = getBrowserType();\n  const options: playwright.LaunchOptions = {\n    ...opts,\n    headless: process.argv.includes('--headless')\n  };\n\n  const index = process.argv.indexOf('--executablePath');\n  if (index > 0 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {\n    options.executablePath = process.argv[index + 1];\n  }\n\n  return browserType.launch(options);\n}\n"
  },
  {
    "path": "test/playwright/playwright.config.ts",
    "content": "import { PlaywrightTestConfig } from '@playwright/test';\n\nconst config: PlaywrightTestConfig = {\n  testDir: '.',\n  timeout: 10000,\n  projects: [\n    {\n      name: 'Chromium',\n      use: {\n        browserName: 'chromium'\n      }\n    },\n    {\n      name: 'FirefoxStable',\n      use: {\n        browserName: 'firefox'\n      }\n    },\n    {\n      name: 'WebKit',\n      use: {\n        browserName: 'webkit'\n      }\n    }\n  ],\n  reporter: 'list',\n  webServer: {\n    command: 'npm start',\n    port: 3000,\n    timeout: 120000,\n    reuseExistingServer: !process.env.CI\n  }\n};\nexport default config;\n"
  },
  {
    "path": "test/playwright/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\n      \"dom\",\n      \"es6\"\n    ],\n    \"rootDir\": \".\",\n    \"outDir\": \"../../out-test/playwright\",\n    \"types\": [\n      \"../../node_modules/@types/node\",\n      \"../../node_modules/@lunapaint/png-codec\"\n    ],\n    \"sourceMap\": true,\n    \"removeComments\": true,\n    \"pretty\": true,\n    \"strict\": true,\n    \"noImplicitAny\": false,\n    \"declaration\": true,\n    \"moduleResolution\": \"node\",\n    \"paths\": {\n      \"browser/*\": [\n        \"../../src/browser/*\"\n      ],\n      \"common/*\": [\n        \"../../src/common/*\"\n      ]\n    },\n    \"target\": \"ESNext\",\n    \"module\": \"commonjs\",\n    \"composite\": true\n  },\n  \"include\": [\n    \"./**/*\",\n    \"../../typings/xterm.d.ts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../../src/browser\"\n    },\n    {\n      \"path\": \"../../src/common\"\n    }\n  ]\n}\n"
  },
  {
    "path": "tsconfig.all.json",
    "content": "{\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    { \"path\": \"./demo\" },\n    { \"path\": \"./src/browser\" },\n    { \"path\": \"./src/headless\" },\n    { \"path\": \"./test/benchmark\" },\n    { \"path\": \"./test/playwright\" },\n    { \"path\": \"./addons/addon-attach\" },\n    { \"path\": \"./addons/addon-clipboard\" },\n    { \"path\": \"./addons/addon-fit\" },\n    { \"path\": \"./addons/addon-image\" },\n    { \"path\": \"./addons/addon-ligatures\" },\n    { \"path\": \"./addons/addon-progress\" },\n    { \"path\": \"./addons/addon-search\" },\n    { \"path\": \"./addons/addon-serialize\" },\n    { \"path\": \"./addons/addon-unicode11\" },\n    { \"path\": \"./addons/addon-unicode-graphemes\" },\n    { \"path\": \"./addons/addon-web-fonts\" },\n    { \"path\": \"./addons/addon-web-links\" },\n    { \"path\": \"./addons/addon-webgl\" }\n  ]\n}\n"
  },
  {
    "path": "typings/xterm-headless.d.ts",
    "content": "/**\n * @license MIT\n *\n * This contains the type declarations for the xterm.js library. Note that\n * some interfaces differ between this file and the actual implementation in\n * src/, that's because this file declares the *public* API which is intended\n * to be stable and consumed by external programs.\n */\n\ndeclare module '@xterm/headless' {\n  /**\n   * A string representing log level.\n   */\n  export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';\n\n  /**\n   * An object containing options for the terminal.\n   */\n  export interface ITerminalOptions {\n    /**\n     * Whether to allow the use of proposed API. When false, any usage of APIs\n     * marked as experimental/proposed will throw an error. The default is\n     * false.\n     */\n    allowProposedApi?: boolean;\n\n    /**\n     * Whether background should support non-opaque color. It must be set before\n     * executing the `Terminal.open()` method and can't be changed later without\n     * executing it again. Note that enabling this can negatively impact\n     * performance.\n     */\n    allowTransparency?: boolean;\n\n    /**\n     * If enabled, alt + click will move the prompt cursor to position\n     * underneath the mouse. The default is true.\n     */\n    altClickMovesCursor?: boolean;\n\n    /**\n     * When enabled the cursor will be set to the beginning of the next line\n     * with every new line. This is equivalent to sending '\\r\\n' for each '\\n'.\n     * Normally the termios settings of the underlying PTY deals with the\n     * translation of '\\n' to '\\r\\n' and this setting should not be used. If you\n     * deal with data from a non-PTY related source, this settings might be\n     * useful.\n     */\n    convertEol?: boolean;\n\n    /**\n     * Whether the cursor blinks. The blinking will stop after 5 minutes of idle\n     * time (refreshed by clicking, focusing or the cursor moving). The default\n     * is false.\n     */\n    cursorBlink?: boolean;\n\n    /**\n     * The interval in milliseconds for the blink attribute. This is the amount\n     * of time text remains visible or hidden before toggling. Set to 0 to\n     * disable blinking. The default is 0.\n     */\n    blinkIntervalDuration?: number;\n\n    /**\n     * The style of the cursor.\n     */\n    cursorStyle?: 'block' | 'underline' | 'bar';\n\n    /**\n     * The width of the cursor in CSS pixels when `cursorStyle` is set to 'bar'.\n     */\n    cursorWidth?: number;\n\n    /**\n     * Whether input should be disabled.\n     */\n    disableStdin?: boolean;\n\n    /**\n     * Whether to draw bold text in bright colors. The default is true.\n     */\n    drawBoldTextInBrightColors?: boolean;\n\n    /**\n     * The spacing in whole pixels between characters.\n     */\n    letterSpacing?: number;\n\n    /**\n     * The line height used to render text.\n     */\n    lineHeight?: number;\n\n    /**\n     * What log level to use, this will log for all levels below and including\n     * what is set:\n     *\n     * 1. trace\n     * 2. debug\n     * 3. info (default)\n     * 4. warn\n     * 5. error\n     * 6. off\n     */\n    logLevel?: LogLevel;\n\n    /**\n     * A logger to use instead of `console`.\n     */\n    logger?: ILogger | null;\n\n    /**\n     * Whether to treat option as the meta key.\n     */\n    macOptionIsMeta?: boolean;\n\n    /**\n     * Whether holding a modifier key will force normal selection behavior,\n     * regardless of whether the terminal is in mouse events mode. This will\n     * also prevent mouse events from being emitted by the terminal. For\n     * example, this allows you to use xterm.js' regular selection inside tmux\n     * with mouse mode enabled.\n     */\n    macOptionClickForcesSelection?: boolean;\n\n    /**\n     * The minimum contrast ratio for text in the terminal, setting this will\n     * change the foreground color dynamically depending on whether the contrast\n     * ratio is met. Example values:\n     *\n     * - 1: The default, do nothing.\n     * - 4.5: Minimum for WCAG AA compliance.\n     * - 7: Minimum for WCAG AAA compliance.\n     * - 21: White on black or black on white.\n     */\n    minimumContrastRatio?: number;\n\n    /**\n     * Whether to reflow the line containing the cursor when the terminal is\n     * resized. Defaults to false, because shells usually handle this\n     * themselves. Note that this will not move the cursor position, only the\n     * line contents.\n     */\n    reflowCursorLine?: boolean;\n\n    /**\n     * Whether to rescale glyphs horizontally that are a single cell wide but\n     * have glyphs that would overlap following cell(s). This typically happens\n     * for ambiguous width characters (eg. the roman numeral characters U+2160+)\n     * which aren't featured in monospace fonts. This is an important feature\n     * for achieving GB18030 compliance.\n     *\n     * The following glyphs will never be rescaled:\n     *\n     * - Emoji glyphs\n     * - Powerline glyphs\n     * - Nerd font glyphs\n     *\n     * Note that this doesn't work with the DOM renderer. The default is false.\n     */\n    rescaleOverlappingGlyphs?: boolean;\n\n    /**\n     * Whether to select the word under the cursor on right click, this is\n     * standard behavior in a lot of macOS applications.\n     */\n    rightClickSelectsWord?: boolean;\n\n    /**\n     * Whether screen reader support is enabled. When on this will expose\n     * supporting elements in the DOM to support NVDA on Windows and VoiceOver\n     * on macOS.\n     */\n    screenReaderMode?: boolean;\n\n    /**\n     * The amount of scrollback in the terminal. Scrollback is the amount of\n     * rows that are retained when lines are scrolled beyond the initial\n     * viewport. Defaults to 1000.\n     */\n    scrollback?: number;\n\n    /**\n     * If enabled the Erase in Display All (ED2) escape sequence will push\n     * erased text to scrollback, instead of clearing only the viewport portion.\n     * This emulates PuTTY's default clear screen behavior.\n     */\n    scrollOnEraseInDisplay?: boolean;\n\n    /**\n     * The scrolling speed multiplier used for adjusting normal scrolling speed.\n     */\n    scrollSensitivity?: number;\n\n    /**\n     * The duration to smoothly scroll between the origin and the target in\n     * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly.\n     */\n    smoothScrollDuration?: number;\n\n    /**\n     * The size of tab stops in the terminal.\n     */\n    tabStopWidth?: number;\n\n    /**\n     * The color theme of the terminal.\n     */\n    theme?: ITheme;\n\n    /**\n     * Compatibility information when the pty is known to be hosted on Windows.\n     * Setting this will turn on certain heuristics/workarounds depending on the\n     * values:\n     *\n     * - `if (!!windowsCompat)`\n     *   - When increasing the rows in the terminal, the amount increased into\n     *     the scrollback. This is done because ConPTY does not behave like\n     *     expect scrollback to come back into the viewport, instead it makes\n     *     empty rows at of the viewport. Not having this behavior can result in\n     *     missing data as the rows get replaced.\n     * - `if !(backend === 'conpty' && buildNumber >= 21376)`\n     *   - Reflow is disabled\n     *   - Lines are assumed to be wrapped if the last character of the line is\n     *     not whitespace.\n     */\n    windowsPty?: IWindowsPty;\n\n    /**\n     * A string containing all characters that are considered word separated by\n     * the double click to select work logic.\n     */\n    wordSeparator?: string;\n\n    /**\n     * Enable various window manipulation and report features.\n     * All features are disabled by default for security reasons.\n     */\n    windowOptions?: IWindowOptions;\n\n    /**\n     * Enable various VT extensions.\n     */\n    vtExtensions?: IVtExtensions;\n  }\n\n  /**\n   * An object containing additional options for the terminal that can only be\n   * set on start up.\n   */\n  export interface ITerminalInitOnlyOptions {\n    /**\n     * The number of columns in the terminal.\n     */\n    cols?: number;\n\n    /**\n     * The number of rows in the terminal.\n     */\n    rows?: number;\n\n    /**\n     * Whether to show the cursor immediately when the terminal is created.\n     * When false (default), the cursor will not be visible until the terminal\n     * is focused for the first time.\n     */\n    showCursorImmediately?: boolean;\n  }\n\n  /**\n   * Contains colors to theme the terminal with.\n   */\n  export interface ITheme {\n    /** The default foreground color */\n    foreground?: string;\n    /** The default background color */\n    background?: string;\n    /** The cursor color */\n    cursor?: string;\n    /** The accent color of the cursor (fg color for a block cursor) */\n    cursorAccent?: string;\n    /** The selection background color (can be transparent) */\n    selection?: string;\n    /** ANSI black (eg. `\\x1b[30m`) */\n    black?: string;\n    /** ANSI red (eg. `\\x1b[31m`) */\n    red?: string;\n    /** ANSI green (eg. `\\x1b[32m`) */\n    green?: string;\n    /** ANSI yellow (eg. `\\x1b[33m`) */\n    yellow?: string;\n    /** ANSI blue (eg. `\\x1b[34m`) */\n    blue?: string;\n    /** ANSI magenta (eg. `\\x1b[35m`) */\n    magenta?: string;\n    /** ANSI cyan (eg. `\\x1b[36m`) */\n    cyan?: string;\n    /** ANSI white (eg. `\\x1b[37m`) */\n    white?: string;\n    /** ANSI bright black (eg. `\\x1b[1;30m`) */\n    brightBlack?: string;\n    /** ANSI bright red (eg. `\\x1b[1;31m`) */\n    brightRed?: string;\n    /** ANSI bright green (eg. `\\x1b[1;32m`) */\n    brightGreen?: string;\n    /** ANSI bright yellow (eg. `\\x1b[1;33m`) */\n    brightYellow?: string;\n    /** ANSI bright blue (eg. `\\x1b[1;34m`) */\n    brightBlue?: string;\n    /** ANSI bright magenta (eg. `\\x1b[1;35m`) */\n    brightMagenta?: string;\n    /** ANSI bright cyan (eg. `\\x1b[1;36m`) */\n    brightCyan?: string;\n    /** ANSI bright white (eg. `\\x1b[1;37m`) */\n    brightWhite?: string;\n    /** ANSI extended colors (16-255) */\n    extendedAnsi?: string[];\n  }\n\n  /**\n   * Pty information for Windows.\n   */\n  export interface IWindowsPty {\n    /**\n     * What pty emulation backend is being used.\n     */\n    backend?: 'conpty' | 'winpty';\n    /**\n     * The Windows build version (eg. 19045)\n     */\n    buildNumber?: number;\n  }\n\n  /**\n   * Enable VT extensions that are not part of the core VT specification.\n   */\n  export interface IVtExtensions {\n    /**\n     * Whether the [kitty keyboard protocol][0] (`CSI =|?|>|< u`) is enabled.\n     * When enabled, the terminal will respond to keyboard protocol queries and\n     * allow programs to enable enhanced keyboard reporting. The default is\n     * false.\n     *\n     * [0]: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n     */\n    kittyKeyboard?: boolean;\n\n    /**\n     * Whether [SGR 221 (not bold) and SGR 222 (not faint) are enabled][0].\n     * These are kitty extensions that allow resetting bold and faint\n     * independently. The default is true.\n     *\n     * [0]: https://sw.kovidgoyal.net/kitty/misc-protocol/\n     */\n    kittySgrBoldFaintControl?: boolean;\n\n    /**\n     * Whether [win32-input-mode][0] (`DECSET 9001`) is enabled. When enabled,\n     * the terminal will allow programs to enable win32 INPUT_RECORD  keyboard\n     * reporting via `CSI ? 9001 h`. The default is false.\n     *\n     * [0]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md\n     */\n    win32InputMode?: boolean;\n\n    /**\n     * Whether [color scheme query and notification][0] (`CSI ? 996 n` and\n     * `DECSET 2031`) is enabled. When enabled, the terminal will respond to\n     * color scheme queries with `CSI ? 997 ; 1 n` (dark) or `CSI ? 997 ; 2 n`\n     * (light) based on the relative luminance of the background and foreground\n     * theme colors. Programs can enable unsolicited notifications via\n     * `CSI ? 2031 h`. The default is true.\n     *\n     * [0]: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/\n     */\n    colorSchemeQuery?: boolean;\n  }\n\n  /**\n   * A replacement logger for `console`.\n   */\n  export interface ILogger {\n    /**\n     * Log a trace message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to trace.\n     */\n    trace(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to debug or below.\n     */\n    debug(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to info or below.\n     */\n    info(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to warn or below.\n     */\n    warn(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to error or below.\n     */\n    error(message: string | Error, ...args: any[]): void;\n  }\n\n  /**\n   * An object that can be disposed via a dispose function.\n   */\n  export interface IDisposable {\n    dispose(): void;\n  }\n\n  /**\n   * An event that can be listened to.\n   * @returns an `IDisposable` to stop listening.\n   */\n  export interface IEvent<T, U = void> {\n    (listener: (arg1: T, arg2: U) => any): IDisposable;\n  }\n\n  /**\n   * Represents a specific line in the terminal that is tracked when scrollback\n   * is trimmed and lines are added or removed. This is a single line that may\n   * be part of a larger wrapped line.\n   */\n  export interface IMarker extends IDisposableWithEvent {\n    /**\n     * A unique identifier for this marker.\n     */\n    readonly id: number;\n\n    /**\n     * The actual line index in the buffer at this point in time. This is set to\n     * -1 if the marker has been disposed.\n     */\n    readonly line: number;\n  }\n\n  /**\n   * Represents a disposable that tracks is disposed state.\n   */\n  export interface IDisposableWithEvent extends IDisposable {\n    /**\n     * Event listener to get notified when this gets disposed.\n     */\n    onDispose: IEvent<void>;\n\n    /**\n     * Whether this is disposed.\n     */\n    readonly isDisposed: boolean;\n  }\n\n  /**\n   * The set of localizable strings.\n   */\n  export interface ILocalizableStrings {\n    /**\n     * The aria label for the underlying input textarea for the terminal.\n     */\n    promptLabel: string;\n\n    /**\n     * Announcement for when line reading is suppressed due to too many lines\n     * being printed to the terminal when `screenReaderMode` is enabled.\n     */\n    tooMuchOutput: string;\n  }\n\n  /**\n   * Enable various window manipulation and report features\n   * (`CSI Ps ; Ps ; Ps t`).\n   *\n   * Most settings have no default implementation, as they heavily rely on\n   * the embedding environment.\n   *\n   * To implement a feature, create a custom CSI hook like this:\n   * ```ts\n   * term.parser.addCsiHandler({final: 't'}, params => {\n   *   const ps = params[0];\n   *   switch (ps) {\n   *     case XY:\n   *       ...            // your implementation for option XY\n   *       return true;   // signal Ps=XY was handled\n   *   }\n   *   return false;      // any Ps that was not handled\n   * });\n   * ```\n   *\n   * Note on security:\n   * Most features are meant to deal with some information of the host machine\n   * where the terminal runs on. This is seen as a security risk possibly\n   * leaking sensitive data of the host to the program in the terminal.\n   * Therefore all options (even those without a default implementation) are\n   * guarded by the boolean flag and disabled by default.\n   */\n  export interface IWindowOptions {\n    /**\n     * Ps=1    De-iconify window.\n     * No default implementation.\n     */\n    restoreWin?: boolean;\n    /**\n     * Ps=2    Iconify window.\n     * No default implementation.\n     */\n    minimizeWin?: boolean;\n    /**\n     * Ps=3 ; x ; y\n     * Move window to [x, y].\n     * No default implementation.\n     */\n    setWinPosition?: boolean;\n    /**\n     * Ps = 4 ; height ; width\n     * Resize the window to given `height` and `width` in pixels.\n     * Omitted parameters should reuse the current height or width.\n     * Zero parameters should use the display's height or width.\n     * No default implementation.\n     */\n    setWinSizePixels?: boolean;\n    /**\n     * Ps=5    Raise the window to the front of the stacking order.\n     * No default implementation.\n     */\n    raiseWin?: boolean;\n    /**\n     * Ps=6    Lower the xterm window to the bottom of the stacking order.\n     * No default implementation.\n     */\n    lowerWin?: boolean;\n    /** Ps=7    Refresh the window. */\n    refreshWin?: boolean;\n    /**\n     * Ps = 8 ; height ; width\n     * Resize the text area to given height and width in characters.\n     * Omitted parameters should reuse the current height or width.\n     * Zero parameters use the display's height or width.\n     * No default implementation.\n     */\n    setWinSizeChars?: boolean;\n    /**\n     * Ps=9 ; 0   Restore maximized window.\n     * Ps=9 ; 1   Maximize window (i.e., resize to screen size).\n     * Ps=9 ; 2   Maximize window vertically.\n     * Ps=9 ; 3   Maximize window horizontally.\n     * No default implementation.\n     */\n    maximizeWin?: boolean;\n    /**\n     * Ps=10 ; 0  Undo full-screen mode.\n     * Ps=10 ; 1  Change to full-screen.\n     * Ps=10 ; 2  Toggle full-screen.\n     * No default implementation.\n     */\n    fullscreenWin?: boolean;\n    /** Ps=11   Report xterm window state.\n     * If the xterm window is non-iconified, it returns \"CSI 1 t\".\n     * If the xterm window is iconified, it returns \"CSI 2 t\".\n     * No default implementation.\n     */\n    getWinState?: boolean;\n    /**\n     * Ps=13      Report xterm window position. Result is \"CSI 3 ; x ; y t\".\n     * Ps=13 ; 2  Report xterm text-area position. Result is \"CSI 3 ; x ; y t\".\n     * No default implementation.\n     */\n    getWinPosition?: boolean;\n    /**\n     * Ps=14      Report xterm text area size in pixels. Result is \"CSI 4 ; height ; width t\".\n     * Ps=14 ; 2  Report xterm window size in pixels. Result is \"CSI  4 ; height ; width t\".\n     * Has a default implementation.\n     */\n    getWinSizePixels?: boolean;\n    /**\n     * Ps=15    Report size of the screen in pixels. Result is \"CSI 5 ; height ; width t\".\n     * No default implementation.\n     */\n    getScreenSizePixels?: boolean;\n    /**\n     * Ps=16  Report xterm character cell size in pixels. Result is \"CSI 6 ; height ; width t\".\n     * Has a default implementation.\n     */\n    getCellSizePixels?: boolean;\n    /**\n     * Ps=18  Report the size of the text area in characters. Result is \"CSI 8 ; height ; width t\".\n     * Has a default implementation.\n     */\n    getWinSizeChars?: boolean;\n    /**\n     * Ps=19  Report the size of the screen in characters. Result is \"CSI 9 ; height ; width t\".\n     * No default implementation.\n     */\n    getScreenSizeChars?: boolean;\n    /**\n     * Ps=20  Report xterm window's icon label. Result is \"OSC L label ST\".\n     * No default implementation.\n     */\n    getIconTitle?: boolean;\n    /**\n     * Ps=21  Report xterm window's title. Result is \"OSC l label ST\".\n     * No default implementation.\n     */\n    getWinTitle?: boolean;\n    /**\n     * Ps=22 ; 0  Save xterm icon and window title on stack.\n     * Ps=22 ; 1  Save xterm icon title on stack.\n     * Ps=22 ; 2  Save xterm window title on stack.\n     * All variants have a default implementation.\n     */\n    pushTitle?: boolean;\n    /**\n     * Ps=23 ; 0  Restore xterm icon and window title from stack.\n     * Ps=23 ; 1  Restore xterm icon title from stack.\n     * Ps=23 ; 2  Restore xterm window title from stack.\n     * All variants have a default implementation.\n     */\n    popTitle?: boolean;\n    /**\n     * Ps>=24  Resize to Ps lines (DECSLPP).\n     * DECSLPP is not implemented. This settings is also used to\n     * enable / disable DECCOLM (earlier variant of DECSLPP).\n     */\n    setWinLines?: boolean;\n  }\n\n  /**\n   * The class that represents an xterm.js terminal.\n   */\n  export class Terminal implements IDisposable {\n    /**\n     * The number of rows in the terminal's viewport. Use\n     * `ITerminalOptions.rows` to set this in the constructor and\n     * `Terminal.resize` for when the terminal exists.\n     */\n    readonly rows: number;\n\n    /**\n     * The number of columns in the terminal's viewport. Use\n     * `ITerminalOptions.cols` to set this in the constructor and\n     * `Terminal.resize` for when the terminal exists.\n     */\n    readonly cols: number;\n\n    /**\n     * Access to the terminal's normal and alt buffer.\n     */\n    readonly buffer: IBufferNamespace;\n\n    /**\n     * Get all markers registered against the buffer. If the alt buffer is\n     * active this will always return [].\n     */\n    readonly markers: ReadonlyArray<IMarker>;\n\n    /**\n     * Get the parser interface to register custom escape sequence handlers.\n     */\n    readonly parser: IParser;\n\n    /**\n     * (EXPERIMENTAL) Get the Unicode handling interface\n     * to register and switch Unicode version.\n     */\n    readonly unicode: IUnicodeHandling;\n\n    /**\n     * Gets the terminal modes as set by SM/DECSET.\n     */\n    readonly modes: IModes;\n\n    /**\n     * Gets or sets the terminal options. This supports setting multiple\n     * options.\n     *\n     * @example Get a single option\n     * ```ts\n     * console.log(terminal.options.fontSize);\n     * ```\n     *\n     * @example Set a single option:\n     * ```ts\n     * terminal.options.fontSize = 12;\n     * ```\n     * Note that for options that are object, a new object must be used in order\n     * to take effect as a reference comparison will be done:\n     * ```ts\n     * const newValue = terminal.options.theme;\n     * newValue.background = '#000000';\n     *\n     * // This won't work\n     * terminal.options.theme = newValue;\n     *\n     * // This will work\n     * terminal.options.theme = { ...newValue };\n     * ```\n     *\n     * @example Set multiple options\n     * ```ts\n     * terminal.options = {\n     *   fontSize: 12,\n     *   fontFamily: 'Courier New'\n     * };\n     * ```\n     */\n    options: ITerminalOptions;\n\n    /**\n     * Natural language strings that can be localized.\n     */\n    static strings: ILocalizableStrings;\n\n    /**\n     * Creates a new `Terminal` object.\n     *\n     * @param options An object containing a set of options.\n     */\n    constructor(options?: ITerminalOptions & ITerminalInitOnlyOptions);\n\n    /**\n     * Adds an event listener for when the bell is triggered.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onBell: IEvent<void>;\n\n    /**\n     * Adds an event listener for when a binary event fires. This is used to\n     * enable non UTF-8 conformant binary messages to be sent to the backend.\n     * Currently this is only used for a certain type of mouse reports that\n     * happen to be not UTF-8 compatible.\n     * The event value is a JS string, pass it to the underlying pty as\n     * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onBinary: IEvent<string>;\n\n    /**\n     * Adds an event listener for the cursor moves.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onCursorMove: IEvent<void>;\n\n    /**\n     * Adds an event listener for when a data event fires. This happens for\n     * example when the user types or pastes into the terminal. The event value\n     * is whatever `string` results, in a typical setup, this should be passed\n     * on to the backing pty.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onData: IEvent<string>;\n\n    /**\n     * Adds an event listener for when a line feed is added.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onLineFeed: IEvent<void>;\n\n    /**\n     * Adds an event listener for when rows are _requested_ to be rendered. The\n     * event value contains the start row and end rows of the rendered area\n     * (ranges from `0` to `Terminal.rows - 1`). This differs from the regular\n     * xterm.js in that it doesn't actually do any rendering but requests\n     * rendering from the outside. This is useful for implementing a custom\n     * renderer on top of xterm-headless.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onRender: IEvent<{ start: number, end: number }>;\n\n    /**\n     * Adds an event listener for when data has been parsed by the terminal,\n     * after {@link write} is called. This event is useful to listen for any\n     * changes in the buffer.\n     *\n     * This fires at most once per frame, after data parsing completes. Note\n     * that this can fire when there are still writes pending if there is a lot\n     * of data.\n     */\n    onWriteParsed: IEvent<void>;\n\n    /**\n     * Adds an event listener for when the terminal is resized. The event value\n     * contains the new size.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onResize: IEvent<{ cols: number, rows: number }>;\n\n    /**\n     * Adds an event listener for when a scroll occurs. The event value is the\n     * new position of the viewport.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onScroll: IEvent<number>;\n\n    /**\n     * Adds an event listener for when an OSC 0 or OSC 2 title change occurs.\n     * The event value is the new title.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onTitleChange: IEvent<string>;\n\n    /**\n     * Input data to application side. The data is treated the same way input\n     * typed into the terminal would (ie. the {@link onData} event will fire).\n     * @param data The data to forward to the application.\n     * @param wasUserInput Whether the input is genuine user input. This is true\n     * by default and triggers additionalbehavior like focus or selection\n     * clearing. Set this to false if the data sent should not be treated like\n     * user input would, for example passing an escape sequence to the\n     * application.\n     */\n    input(data: string, wasUserInput?: boolean): void;\n\n    /**\n     * Resizes the terminal. It's best practice to debounce calls to resize,\n     * this will help ensure that the pty can respond to the resize event\n     * before another one occurs.\n     * @param x The number of columns to resize to.\n     * @param y The number of rows to resize to.\n     */\n    resize(columns: number, rows: number): void;\n\n    /**\n     * Adds a marker to the normal buffer and returns it. If the alt buffer is\n     * active, undefined is returned.\n     * @param cursorYOffset The y position offset of the marker from the cursor.\n     * @returns The new marker or undefined.\n     */\n    registerMarker(cursorYOffset?: number): IMarker | undefined;\n\n    /*\n     * Disposes of the terminal, detaching it from the DOM and removing any\n     * active listeners. Once the terminal is disposed it should not be used\n     * again.\n     */\n    dispose(): void;\n\n    /**\n     * Scroll the display of the terminal\n     * @param amount The number of lines to scroll down (negative scroll up).\n     */\n    scrollLines(amount: number): void;\n\n    /**\n     * Scroll the display of the terminal by a number of pages.\n     * @param pageCount The number of pages to scroll (negative scrolls up).\n     */\n    scrollPages(pageCount: number): void;\n\n    /**\n     * Scrolls the display of the terminal to the top.\n     */\n    scrollToTop(): void;\n\n    /**\n     * Scrolls the display of the terminal to the bottom.\n     */\n    scrollToBottom(): void;\n\n    /**\n     * Scrolls to a line within the buffer.\n     * @param line The 0-based line index to scroll to.\n     */\n    scrollToLine(line: number): void;\n\n    /**\n     * Clear the entire buffer, making the prompt line the new first line.\n     */\n    clear(): void;\n\n    /**\n     * Write data to the terminal.\n     *\n     * Note that the change will not be reflected in the {@link buffer}\n     * immediately as the data is processed asynchronously. Provide a\n     * {@link callback} to know when the data was processed.\n     * @param data The data to write to the terminal. This can either be raw\n     * bytes given as Uint8Array from the pty or a string. Raw bytes will always\n     * be treated as UTF-8 encoded, string data as UTF-16.\n     * @param callback Optional callback that fires when the data was processed\n     * by the parser. This callback must be provided and awaited in order for\n     * {@link buffer} to reflect the change in the write.\n     */\n    write(data: string | Uint8Array, callback?: () => void): void;\n\n    /**\n     * Writes data to the terminal, followed by a break line character (\\n).\n     *\n     * Note that the change will not be reflected in the {@link buffer}\n     * immediately as the data is processed asynchronously. Provide a\n     * {@link callback} to know when the data was processed.\n     * @param data The data to write to the terminal. This can either be raw\n     * bytes given as Uint8Array from the pty or a string. Raw bytes will always\n     * be treated as UTF-8 encoded, string data as UTF-16.\n     * @param callback Optional callback that fires when the data was processed\n     * by the parser. This callback must be provided and awaited in order for\n     * {@link buffer} to reflect the change in the write.\n     */\n    writeln(data: string | Uint8Array, callback?: () => void): void;\n\n    /**\n     * Perform a full reset (RIS, aka '\\x1bc').\n     */\n    reset(): void;\n\n    /**\n     * Loads an addon into this instance of xterm.js.\n     * @param addon The addon to load.\n     */\n    loadAddon(addon: ITerminalAddon): void;\n  }\n\n  /**\n   * An addon that can provide additional functionality to the terminal.\n   */\n  export interface ITerminalAddon extends IDisposable {\n    /**\n     * This is called when the addon is activated.\n     */\n    activate(terminal: Terminal): void;\n  }\n\n  /**\n   * An object representing a range within the viewport of the terminal.\n   */\n  export interface IViewportRange {\n    /**\n     * The start of the range.\n     */\n    start: IViewportRangePosition;\n\n    /**\n     * The end of the range.\n     */\n    end: IViewportRangePosition;\n  }\n\n  /**\n   * An object representing a cell position within the viewport of the terminal.\n   */\n  interface IViewportRangePosition {\n    /**\n     * The x position of the cell. This is a 0-based index that refers to the\n     * space in between columns, not the column itself. Index 0 refers to the\n     * left side of the viewport, index `Terminal.cols` refers to the right side\n     * of the viewport. This can be thought of as how a cursor is positioned in\n     * a text editor.\n     */\n    x: number;\n\n    /**\n     * The y position of the cell. This is a 0-based index that refers to a\n     * specific row.\n     */\n    y: number;\n  }\n\n  /**\n   * A range within a buffer.\n   */\n  interface IBufferRange {\n    /**\n     * The start position of the range.\n     */\n    start: IBufferCellPosition;\n\n    /**\n     * The end position of the range.\n     */\n    end: IBufferCellPosition;\n  }\n\n  /**\n   * A position within a buffer.\n   */\n  interface IBufferCellPosition {\n    /**\n     * The x position within the buffer.\n     */\n    x: number;\n\n    /**\n     * The y position within the buffer.\n     */\n    y: number;\n  }\n\n  /**\n   * Represents a terminal buffer.\n   */\n  interface IBuffer {\n    /**\n     * The type of the buffer.\n     */\n    readonly type: 'normal' | 'alternate';\n\n    /**\n     * The y position of the cursor. This ranges between `0` (when the\n     * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the\n     * last row).\n     */\n    readonly cursorY: number;\n\n    /**\n     * The x position of the cursor. This ranges between `0` (left side) and\n     * `Terminal.cols` (after last cell of the row).\n     */\n    readonly cursorX: number;\n\n    /**\n     * The line within the buffer where the top of the viewport is.\n     */\n    readonly viewportY: number;\n\n    /**\n     * The line within the buffer where the top of the bottom page is (when\n     * fully scrolled down).\n     */\n    readonly baseY: number;\n\n    /**\n     * The amount of lines in the buffer.\n     */\n    readonly length: number;\n\n    /**\n     * Gets a line from the buffer, or undefined if the line index does not\n     * exist.\n     *\n     * Note that the result of this function should be used immediately after\n     * calling as when the terminal updates it could lead to unexpected\n     * behavior.\n     *\n     * @param y The line index to get.\n     */\n    getLine(y: number): IBufferLine | undefined;\n\n    /**\n     * Creates an empty cell object suitable as a cell reference in\n     * `line.getCell(x, cell)`. Use this to avoid costly recreation of\n     * cell objects when dealing with tons of cells.\n     */\n    getNullCell(): IBufferCell;\n  }\n\n  /**\n   * Represents the terminal's set of buffers.\n   */\n  interface IBufferNamespace {\n    /**\n     * The active buffer, this will either be the normal or alternate buffers.\n     */\n    readonly active: IBuffer;\n\n    /**\n     * The normal buffer.\n     */\n    readonly normal: IBuffer;\n\n    /**\n     * The alternate buffer, this becomes the active buffer when an application\n     * enters this mode via DECSET (`CSI ? 4 7 h`)\n     */\n    readonly alternate: IBuffer;\n\n    /**\n     * Adds an event listener for when the active buffer changes.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onBufferChange: IEvent<IBuffer>;\n  }\n\n  /**\n   * Represents a line in the terminal's buffer.\n   */\n  interface IBufferLine {\n    /**\n     * Whether the line is wrapped from the previous line.\n     */\n    readonly isWrapped: boolean;\n\n    /**\n     * The length of the line, all call to getCell beyond the length will result\n     * in `undefined`.\n     */\n    readonly length: number;\n\n    /**\n     * Gets a cell from the line, or undefined if the line index does not exist.\n     *\n     * Note that the result of this function should be used immediately after\n     * calling as when the terminal updates it could lead to unexpected\n     * behavior.\n     *\n     * @param x The character index to get.\n     * @param cell Optional cell object to load data into for performance\n     * reasons. This is mainly useful when every cell in the buffer is being\n     * looped over to avoid creating new objects for every cell.\n     */\n    getCell(x: number, cell?: IBufferCell): IBufferCell | undefined;\n\n    /**\n     * Gets the line as a string. Note that this is gets only the string for the\n     * line, not taking isWrapped into account.\n     *\n     * @param trimRight Whether to trim any whitespace at the right of the line.\n     * @param startColumn The column to start from (inclusive).\n     * @param endColumn The column to end at (exclusive).\n     */\n    translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;\n  }\n\n  /**\n   * Represents a single cell in the terminal's buffer.\n   */\n  interface IBufferCell {\n    /**\n     * The width of the character. Some examples:\n     *\n     * - `1` for most cells.\n     * - `2` for wide character like CJK glyphs.\n     * - `0` for cells immediately following cells with a width of `2`.\n     */\n    getWidth(): number;\n\n    /**\n     * The character(s) within the cell. Examples of what this can contain:\n     *\n     * - A normal width character\n     * - A wide character (eg. CJK)\n     * - An emoji\n     */\n    getChars(): string;\n\n    /**\n     * Gets the UTF32 codepoint of single characters, if content is a combined\n     * string it returns the codepoint of the last character in the string.\n     */\n    getCode(): number;\n\n    /**\n     * Gets the number representation of the foreground color mode, this can be\n     * used to perform quick comparisons of 2 cells to see if they're the same.\n     * Use `isFgRGB`, `isFgPalette` and `isFgDefault` to check what color mode\n     * a cell is.\n     */\n    getFgColorMode(): number;\n\n    /**\n     * Gets the number representation of the background color mode, this can be\n     * used to perform quick comparisons of 2 cells to see if they're the same.\n     * Use `isBgRGB`, `isBgPalette` and `isBgDefault` to check what color mode\n     * a cell is.\n     */\n    getBgColorMode(): number;\n\n    /**\n     * Gets a cell's foreground color number, this differs depending on what the\n     * color mode of the cell is:\n     *\n     * - Default: This should be 0, representing the default foreground color\n     *   (CSI 39 m).\n     * - Palette: This is a number from 0 to 255 of ANSI colors (CSI 3(0-7) m,\n     *   CSI 9(0-7) m, CSI 38 ; 5 ; 0-255 m).\n     * - RGB: A hex value representing a 'true color': 0xRRGGBB.\n     *   (CSI 3 8 ; 2 ; Pi ; Pr ; Pg ; Pb)\n     */\n    getFgColor(): number;\n\n    /**\n     * Gets a cell's background color number, this differs depending on what the\n     * color mode of the cell is:\n     *\n     * - Default: This should be 0, representing the default background color\n     *   (CSI 49 m).\n     * - Palette: This is a number from 0 to 255 of ANSI colors\n     *   (CSI 4(0-7) m, CSI 10(0-7) m, CSI 48 ; 5 ; 0-255 m).\n     * - RGB: A hex value representing a 'true color': 0xRRGGBB\n     *   (CSI 4 8 ; 2 ; Pi ; Pr ; Pg ; Pb)\n     */\n    getBgColor(): number;\n\n    /** Whether the cell has the bold attribute (CSI 1 m). */\n    isBold(): number;\n    /** Whether the cell has the italic attribute (CSI 3 m). */\n    isItalic(): number;\n    /** Whether the cell has the dim attribute (CSI 2 m). */\n    isDim(): number;\n    /** Whether the cell has the underline attribute (CSI 4 m). */\n    isUnderline(): number;\n    /** Whether the cell has the blink attribute (CSI 5 m). */\n    isBlink(): number;\n    /** Whether the cell has the inverse attribute (CSI 7 m). */\n    isInverse(): number;\n    /** Whether the cell has the invisible attribute (CSI 8 m). */\n    isInvisible(): number;\n    /** Whether the cell has the strikethrough attribute (CSI 9 m). */\n    isStrikethrough(): number;\n    /** Whether the cell has the overline attribute (CSI 53 m). */\n    isOverline(): number;\n\n    /** Whether the cell is using the RGB foreground color mode. */\n    isFgRGB(): boolean;\n    /** Whether the cell is using the RGB background color mode. */\n    isBgRGB(): boolean;\n    /** Whether the cell is using the palette foreground color mode. */\n    isFgPalette(): boolean;\n    /** Whether the cell is using the palette background color mode. */\n    isBgPalette(): boolean;\n    /** Whether the cell is using the default foreground color mode. */\n    isFgDefault(): boolean;\n    /** Whether the cell is using the default background color mode. */\n    isBgDefault(): boolean;\n\n    /** Whether the cell has the default attribute (no color or style). */\n    isAttributeDefault(): boolean;\n\n    /** Gets the underline style. */\n    getUnderlineStyle(): number;\n    /** Gets the underline color number. */\n    getUnderlineColor(): number;\n    /** Gets the underline color mode. */\n    getUnderlineColorMode(): number;\n    /** Whether the cell is using the RGB underline color mode. */\n    isUnderlineColorRGB(): boolean;\n    /** Whether the cell is using the palette underline color mode. */\n    isUnderlineColorPalette(): boolean;\n    /** Whether the cell is using the default underline color mode. */\n    isUnderlineColorDefault(): boolean;\n\n    /**\n     * Compares the cell's attributes (colors and styles) with another cell.\n     * This does not compare the cell's content and excludes URL ids and\n     * underline variant offsets.\n     */\n    attributesEquals(other: IBufferCell): boolean;\n  }\n\n  /**\n   * Data type to register a CSI, DCS or ESC callback in the parser\n   * in the form:\n   *    ESC I..I F\n   *    CSI Prefix P..P I..I F\n   *    DCS Prefix P..P I..I F data_bytes ST\n   *\n   * with these rules/restrictions:\n   * - prefix can only be used with CSI and DCS\n   * - only one leading prefix byte is recognized by the parser\n   *   before any other parameter bytes (P..P)\n   * - intermediate bytes are recognized up to 2\n   *\n   * For custom sequences make sure to read ECMA-48 and the resources at\n   * vt100.net to not clash with existing sequences or reserved address space.\n   * General recommendations:\n   * - use private address space (see ECMA-48)\n   * - use max one intermediate byte (technically not limited by the spec,\n   *   in practice there are no sequences with more than one intermediate byte,\n   *   thus parsers might get confused with more intermediates)\n   * - test against other common emulators to check whether they escape/ignore\n   *   the sequence correctly\n   *\n   * Notes: OSC command registration is handled differently (see addOscHandler)\n   *        APC, PM or SOS is currently not supported.\n   */\n  export interface IFunctionIdentifier {\n    /**\n     * Optional prefix byte, must be in range \\x3c .. \\x3f.\n     * Usable in CSI and DCS.\n     */\n    prefix?: string;\n    /**\n     * Optional intermediate bytes, must be in range \\x20 .. \\x2f.\n     * Usable in CSI, DCS and ESC.\n     */\n    intermediates?: string;\n    /**\n     * Final byte, must be in range \\x40 .. \\x7e for CSI and DCS,\n     * \\x30 .. \\x7e for ESC.\n     */\n    final: string;\n  }\n\n  /**\n   * Allows hooking into the parser for custom handling of escape sequences.\n   */\n  export interface IParser {\n    /**\n     * Adds a handler for CSI escape sequences.\n     * @param id Specifies the function identifier under which the callback\n     * gets registered, e.g. {final: 'm'} for SGR.\n     * @param callback The function to handle the sequence. The callback is\n     * called with the numerical params. If the sequence has subparams the\n     * array will contain subarrays with their numercial values.\n     * Return true if the sequence was handled; false if we should try\n     * a previous handler (set by addCsiHandler or setCsiHandler).\n     * The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;\n\n    /**\n     * Adds a handler for DCS escape sequences.\n     * @param id Specifies the function identifier under which the callback\n     * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.\n     * @param callback The function to handle the sequence. Note that the\n     * function will only be called once if the sequence finished sucessfully.\n     * There is currently no way to intercept smaller data chunks, data chunks\n     * will be stored up until the sequence is finished. Since DCS sequences\n     * are not limited by the amount of data this might impose a problem for\n     * big payloads. Currently xterm.js limits DCS payload to 10 MB\n     * which should give enough room for most use cases.\n     * The function gets the payload and numerical parameters as arguments.\n     * Return true if the sequence was handled; false if we should try\n     * a previous handler (set by addDcsHandler or setDcsHandler).\n     * The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;\n\n    /**\n     * Adds a handler for ESC escape sequences.\n     * @param id Specifies the function identifier under which the callback\n     * gets registered, e.g. {intermediates: '%' final: 'G'} for\n     * default charset selection.\n     * @param handler The function to handle the sequence.\n     * Return true if the sequence was handled; false if we should try\n     * a previous handler (set by addEscHandler or setEscHandler).\n     * The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;\n\n    /**\n     * Adds a handler for OSC escape sequences.\n     * @param ident The number (first parameter) of the sequence.\n     * @param callback The function to handle the sequence. Note that the\n     * function will only be called once if the sequence finished sucessfully.\n     * There is currently no way to intercept smaller data chunks, data chunks\n     * will be stored up until the sequence is finished. Since OSC sequences\n     * are not limited by the amount of data this might impose a problem for\n     * big payloads. Currently xterm.js limits OSC payload to 10 MB\n     * which should give enough room for most use cases.\n     * The callback is called with OSC data string.\n     * Return true if the sequence was handled; false if we should try\n     * a previous handler (set by addOscHandler or setOscHandler).\n     * The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;\n\n    /**\n     * Adds a handler for APC escape sequences.\n     * @param ident The identifier (first character) of the sequence as a\n     * character code, e.g. 71 for 'G' (Kitty graphics protocol).\n     * @param callback The function to handle the sequence. Note that the\n     * function will only be called once if the sequence finished successfully.\n     * There is currently no way to intercept smaller data chunks, data chunks\n     * will be stored up until the sequence is finished. Since APC sequences are\n     * not limited by the amount of data this might impose a problem for big\n     * payloads. Currently xterm.js limits APC payload to 10 MB which should\n     * give enough room for most use cases. The callback is called with APC data\n     * string (excluding the identifier character). Return true if the sequence\n     * was handled; false if we should try a previous handler. The most recently\n     * added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerApcHandler(ident: number, callback: (data: string) => boolean): IDisposable;\n  }\n\n  /**\n   * (EXPERIMENTAL) Unicode version provider.\n   * Used to register custom Unicode versions with `Terminal.unicode.register`.\n   */\n  export interface IUnicodeVersionProvider {\n    /**\n     * String indicating the Unicode version provided.\n     */\n    readonly version: string;\n\n    /**\n     * Unicode version dependent wcwidth implementation.\n     */\n    wcwidth(codepoint: number): 0 | 1 | 2;\n    charProperties(codepoint: number, preceding: number): number;\n  }\n\n  /**\n   * (EXPERIMENTAL) Unicode handling interface.\n   */\n  export interface IUnicodeHandling {\n    /**\n     * Register a custom Unicode version provider.\n     */\n    register(provider: IUnicodeVersionProvider): void;\n\n    /**\n     * Registered Unicode versions.\n     */\n    readonly versions: ReadonlyArray<string>;\n\n    /**\n     * Getter/setter for active Unicode version.\n     */\n    activeVersion: string;\n  }\n\n  /**\n   * Terminal modes as set by SM/DECSET.\n   */\n  export interface IModes {\n    /**\n     * Application Cursor Keys (DECCKM): `CSI ? 1 h`\n     */\n    readonly applicationCursorKeysMode: boolean;\n    /**\n     * Application Keypad Mode (DECNKM): `CSI ? 6 6 h`\n     */\n    readonly applicationKeypadMode: boolean;\n    /**\n     * Bracketed Paste Mode: `CSI ? 2 0 0 4 h`\n     */\n    readonly bracketedPasteMode: boolean;\n    /**\n     * Insert Mode (IRM): `CSI 4 h`\n     */\n    readonly insertMode: boolean;\n    /**\n     * Mouse Tracking, this can be one of the following:\n     * - none: This is the default value and can be reset with DECRST\n     * - x10: Send Mouse X & Y on button press `CSI ? 9 h`\n     * - vt200: Send Mouse X & Y on button press and release `CSI ? 1 0 0 0 h`\n     * - drag: Use Cell Motion Mouse Tracking `CSI ? 1 0 0 2 h`\n     * - any: Use All Motion Mouse Tracking `CSI ? 1 0 0 3 h`\n     */\n    readonly mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any';\n    /**\n     * Origin Mode (DECOM): `CSI ? 6 h`\n     */\n    readonly originMode: boolean;\n    /**\n     * Reverse-wraparound Mode: `CSI ? 4 5 h`\n     */\n    readonly reverseWraparoundMode: boolean;\n    /**\n     * Send FocusIn/FocusOut events: `CSI ? 1 0 0 4 h`\n     */\n    readonly sendFocusMode: boolean;\n    /**\n     * Show Cursor (DECTCEM): `CSI ? 2 5 h`\n     */\n    readonly showCursor: boolean;\n    /**\n     * Synchronized Output Mode: `CSI ? 2 0 2 6 h`\n     *\n     * When enabled, output is buffered and only rendered when the mode is\n     * disabled, allowing for atomic screen updates without tearing.\n     */\n    readonly synchronizedOutputMode: boolean;\n    /**\n     * Win32 Input Mode: `CSI ? 9 0 0 1 h`\n     *\n     * When enabled, keyboard input is sent as Win32 INPUT_RECORD format:\n     * `CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _`\n     */\n    readonly win32InputMode: boolean;\n    /**\n     * Auto-Wrap Mode (DECAWM): `CSI ? 7 h`\n     */\n    readonly wraparoundMode: boolean;\n  }\n}\n"
  },
  {
    "path": "typings/xterm.d.ts",
    "content": "/**\n * @license MIT\n *\n * This contains the type declarations for the xterm.js library. Note that\n * some interfaces differ between this file and the actual implementation in\n * src/, that's because this file declares the *public* API which is intended\n * to be stable and consumed by external programs.\n */\n\n/// <reference lib=\"dom\"/>\n\ndeclare module '@xterm/xterm' {\n  /**\n   * A string or number representing text font weight.\n   */\n  export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;\n\n  /**\n   * A string representing log level.\n   */\n  export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';\n\n  /**\n   * An object containing options for the terminal.\n   */\n  export interface ITerminalOptions {\n    /**\n     * Whether to allow the use of proposed API. When false, any usage of APIs\n     * marked as experimental/proposed will throw an error. The default is\n     * false.\n     */\n    allowProposedApi?: boolean;\n\n    /**\n     * Whether background should support non-opaque color. It must be set before\n     * executing the `Terminal.open()` method and can't be changed later without\n     * executing it again. Note that enabling this can negatively impact\n     * performance.\n     */\n    allowTransparency?: boolean;\n\n    /**\n     * If enabled, alt + click will move the prompt cursor to position\n     * underneath the mouse. The default is true.\n     */\n    altClickMovesCursor?: boolean;\n\n    /**\n     * When enabled the cursor will be set to the beginning of the next line\n     * with every new line. This is equivalent to sending `\\r\\n` for each `\\n`.\n     * Normally the settings of the underlying PTY (`termios`) deal with the\n     * translation of `\\n` to `\\r\\n` and this setting should not be used. If you\n     * deal with data from a non-PTY related source, this settings might be\n     * useful.\n     *\n     * @see https://pubs.opengroup.org/onlinepubs/007904975/basedefs/termios.h.html\n     */\n    convertEol?: boolean;\n\n    /**\n     * Whether the cursor blinks. The blinking will stop after 5 minutes of idle\n     * time (refreshed by clicking, focusing or the cursor moving). The default\n     * is false.\n     */\n    cursorBlink?: boolean;\n\n    /**\n     * The interval in milliseconds for the blink attribute. This is the amount\n     * of time text remains visible or hidden before toggling. Set to 0 to\n     * disable blinking. The default is 0.\n     */\n    blinkIntervalDuration?: number;\n\n    /**\n     * The style of the cursor when the terminal is focused.\n     */\n    cursorStyle?: 'block' | 'underline' | 'bar';\n\n    /**\n     * The width of the cursor in CSS pixels when `cursorStyle` is set to 'bar'.\n     */\n    cursorWidth?: number;\n\n    /**\n     * The style of the cursor when the terminal is not focused.\n     */\n    cursorInactiveStyle?: 'outline' | 'block' | 'bar' | 'underline' | 'none';\n\n    /**\n     * Whether input should be disabled.\n     */\n    disableStdin?: boolean;\n\n    /**\n     * A {@link Document} to use instead of the one that xterm.js was attached\n     * to. The purpose of this is to improve support in multi-window\n     * applications where HTML elements may be references across multiple\n     * windows which can cause problems with `instanceof`.\n     *\n     * The type is `any` because using `Document` can cause TS to have\n     * performance/compiler problems.\n     */\n    documentOverride?: any | null;\n\n    /**\n     * Whether to draw bold text in bright colors. The default is true.\n     */\n    drawBoldTextInBrightColors?: boolean;\n\n    /**\n     * The scroll speed multiplier used for fast scrolling when `Alt` is held.\n     */\n    fastScrollSensitivity?: number;\n\n    /**\n     * The font size used to render text.\n     */\n    fontSize?: number;\n\n    /**\n     * The font family used to render text.\n     */\n    fontFamily?: string;\n\n    /**\n     * The font weight used to render non-bold text.\n     */\n    fontWeight?: FontWeight;\n\n    /**\n     * The font weight used to render bold text.\n     */\n    fontWeightBold?: FontWeight;\n\n    /**\n     * Whether to ignore the bracketed paste mode. When true, this will always\n     * paste without the `\\x1b[200~` and `\\x1b[201~` sequences, even when the\n     * shell enables bracketed mode.\n     */\n    ignoreBracketedPasteMode?: boolean;\n\n    /**\n     * The spacing in whole pixels between characters.\n     */\n    letterSpacing?: number;\n\n    /**\n     * The line height used to render text.\n     */\n    lineHeight?: number;\n\n    /**\n     * The handler for OSC 8 hyperlinks. Links will use the `confirm` browser\n     * API with a strongly worded warning if no link handler is set.\n     *\n     * When setting this, consider the security of users opening these links,\n     * at a minimum there should be a tooltip or a prompt when hovering or\n     * activating the link respectively. An example of what might be possible is\n     * a terminal app writing link in the form `javascript:...` that runs some\n     * javascript, a safe approach to prevent that is to validate the link\n     * starts with http(s)://.\n     */\n    linkHandler?: ILinkHandler | null;\n\n    /**\n     * What log level to use, this will log for all levels below and including\n     * what is set:\n     *\n     * 1. trace\n     * 2. debug\n     * 3. info (default)\n     * 4. warn\n     * 5. error\n     * 6. off\n     */\n    logLevel?: LogLevel;\n\n    /**\n     * A logger to use instead of `console`.\n     */\n    logger?: ILogger | null;\n\n    /**\n     * Whether to treat option as the meta key.\n     */\n    macOptionIsMeta?: boolean;\n\n    /**\n     * Whether holding a modifier key will force normal selection behavior,\n     * regardless of whether the terminal is in mouse events mode. This will\n     * also prevent mouse events from being emitted by the terminal. For\n     * example, this allows you to use xterm.js' regular selection inside tmux\n     * with mouse mode enabled.\n     */\n    macOptionClickForcesSelection?: boolean;\n\n    /**\n     * The minimum contrast ratio for text in the terminal, setting this will\n     * change the foreground color dynamically depending on whether the contrast\n     * ratio is met. Example values:\n     *\n     * - 1: The default, do nothing.\n     * - 4.5: Minimum for WCAG AA compliance.\n     * - 7: Minimum for WCAG AAA compliance.\n     * - 21: White on black or black on white.\n     */\n    minimumContrastRatio?: number;\n\n    /**\n     * Control various quirks features that are either non-standard or standard\n     * in but generally rejected in modern terminals.\n     */\n    quirks?: ITerminalQuirks;\n\n    /**\n     * Whether to reflow the line containing the cursor when the terminal is\n     * resized. Defaults to false, because shells usually handle this\n     * themselves. Note that this will not move the cursor position, only the\n     * line contents.\n     */\n    reflowCursorLine?: boolean;\n\n    /**\n     * Whether to rescale glyphs horizontally that are a single cell wide but\n     * have glyphs that would overlap following cell(s). This typically happens\n     * for ambiguous width characters (eg. the roman numeral characters U+2160+)\n     * which aren't featured in monospace fonts. This is an important feature\n     * for achieving GB18030 compliance.\n     *\n     * The following glyphs will never be rescaled:\n     *\n     * - Emoji glyphs\n     * - Powerline glyphs\n     * - Nerd font glyphs\n     *\n     * Note that this doesn't work with the DOM renderer. The default is false.\n     */\n    rescaleOverlappingGlyphs?: boolean;\n\n    /**\n     * Whether to select the word under the cursor on right click, this is\n     * standard behavior in a lot of macOS applications.\n     */\n    rightClickSelectsWord?: boolean;\n\n    /**\n     * Whether screen reader support is enabled. When on this will expose\n     * supporting elements in the DOM to support NVDA on Windows and VoiceOver\n     * on macOS.\n     */\n    screenReaderMode?: boolean;\n\n    /**\n     * The amount of scrollback in the terminal. Scrollback is the amount of\n     * rows that are retained when lines are scrolled beyond the initial\n     * viewport. Defaults to 1000.\n     */\n    scrollback?: number;\n\n    /**\n     * If enabled the Erase in Display All (ED2) escape sequence will push\n     * erased text to scrollback, instead of clearing only the viewport portion.\n     * This emulates PuTTY's default clear screen behavior.\n     */\n    scrollOnEraseInDisplay?: boolean;\n\n    /**\n     * Whether to scroll to the bottom whenever there is some user input. The\n     * default is true.\n     */\n    scrollOnUserInput?: boolean;\n\n    /**\n     * The scrolling speed multiplier used for adjusting normal scrolling speed.\n     */\n    scrollSensitivity?: number;\n\n    /**\n     * Options for configuring the scrollbar.\n     */\n    scrollbar?: IScrollbarOptions;\n\n    /**\n     * The duration to smoothly scroll between the origin and the target in\n     * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly.\n     */\n    smoothScrollDuration?: number;\n\n    /**\n     * The size of tab stops in the terminal.\n     */\n    tabStopWidth?: number;\n\n    /**\n     * The color theme of the terminal.\n     */\n    theme?: ITheme;\n\n    /**\n     * Enable various VT extensions.\n     */\n    vtExtensions?: IVtExtensions;\n\n    /**\n     * Compatibility information when the pty is known to be hosted on Windows.\n     * Setting this will turn on certain heuristics/workarounds depending on the\n     * values:\n     *\n     * - `if (backend !== undefined || buildNumber !== undefined)`\n     *   - When increasing the rows in the terminal, the amount increased into\n     *     the scrollback. This is done because ConPTY does not behave like\n     *     expect scrollback to come back into the viewport, instead it makes\n     *     empty rows at of the viewport. Not having this behavior can result in\n     *     missing data as the rows get replaced.\n     * - `if !(backend === 'conpty' && buildNumber >= 21376)`\n     *   - Reflow is disabled\n     *   - Lines are assumed to be wrapped if the last character of the line is\n     *     not whitespace.\n     */\n    windowsPty?: IWindowsPty;\n\n    /**\n     * A string containing all characters that are considered word separated by\n     * the double click to select work logic.\n     */\n    wordSeparator?: string;\n\n    /**\n     * Enable various window manipulation and report features.\n     * All features are disabled by default for security reasons.\n     */\n    windowOptions?: IWindowOptions;\n  }\n\n  /**\n   * An object containing additional options for the terminal that can only be\n   * set on start up.\n   */\n  export interface ITerminalInitOnlyOptions {\n    /**\n     * The number of columns in the terminal.\n     */\n    cols?: number;\n\n    /**\n     * The number of rows in the terminal.\n     */\n    rows?: number;\n\n    /**\n     * Whether to show the cursor immediately when the terminal is created.\n     * When false (default), the cursor will not be visible until the terminal\n     * is focused for the first time.\n     */\n    showCursorImmediately?: boolean;\n  }\n\n  /**\n   * Contains colors to theme the terminal with.\n   */\n  export interface ITheme {\n    /** The default foreground color */\n    foreground?: string;\n    /** The default background color */\n    background?: string;\n    /** The cursor color */\n    cursor?: string;\n    /** The accent color of the cursor (fg color for a block cursor) */\n    cursorAccent?: string;\n    /** The selection background color (can be transparent) */\n    selectionBackground?: string;\n    /** The selection foreground color */\n    selectionForeground?: string;\n    /**\n     * The selection background color when the terminal does not have focus (can\n     * be transparent)\n     */\n    selectionInactiveBackground?: string;\n    /**\n     * The scrollbar slider background color. Defaults to\n     * {@link ITheme.foreground} with 20% opacity.\n     */\n    scrollbarSliderBackground?: string;\n    /**\n     * The scrollbar slider background color when hovered. Defaults to\n     * {@link ITheme.foreground} with 40% opacity.\n     */\n    scrollbarSliderHoverBackground?: string;\n    /**\n     * The scrollbar slider background color when clicked. Defaults to\n     * {@link ITheme.foreground} with 50% opacity.\n     */\n    scrollbarSliderActiveBackground?: string;\n    /**\n     * The border color of the overview ruler. This visually separates the\n     * terminal from the scroll bar when {@link IScrollbarOptions.width} is set.\n     * When this is not set it defaults to black (`#000000`).\n     */\n    overviewRulerBorder?: string;\n    /** ANSI black (eg. `\\x1b[30m`) */\n    black?: string;\n    /** ANSI red (eg. `\\x1b[31m`) */\n    red?: string;\n    /** ANSI green (eg. `\\x1b[32m`) */\n    green?: string;\n    /** ANSI yellow (eg. `\\x1b[33m`) */\n    yellow?: string;\n    /** ANSI blue (eg. `\\x1b[34m`) */\n    blue?: string;\n    /** ANSI magenta (eg. `\\x1b[35m`) */\n    magenta?: string;\n    /** ANSI cyan (eg. `\\x1b[36m`) */\n    cyan?: string;\n    /** ANSI white (eg. `\\x1b[37m`) */\n    white?: string;\n    /** ANSI bright black (eg. `\\x1b[1;30m`) */\n    brightBlack?: string;\n    /** ANSI bright red (eg. `\\x1b[1;31m`) */\n    brightRed?: string;\n    /** ANSI bright green (eg. `\\x1b[1;32m`) */\n    brightGreen?: string;\n    /** ANSI bright yellow (eg. `\\x1b[1;33m`) */\n    brightYellow?: string;\n    /** ANSI bright blue (eg. `\\x1b[1;34m`) */\n    brightBlue?: string;\n    /** ANSI bright magenta (eg. `\\x1b[1;35m`) */\n    brightMagenta?: string;\n    /** ANSI bright cyan (eg. `\\x1b[1;36m`) */\n    brightCyan?: string;\n    /** ANSI bright white (eg. `\\x1b[1;37m`) */\n    brightWhite?: string;\n    /** ANSI extended colors (16-255) */\n    extendedAnsi?: string[];\n  }\n\n  /**\n   * Control various quirks features that are either non-standard or standard\n   * in but generally rejected in modern terminals.\n   */\n  export interface ITerminalQuirks {\n    /**\n     * Enables support for DECSET 12 and DECRST 12 which controls cursor blink.\n     * Programs such as `vim` may use this to set the cursor blink state but may\n     * not change it back when exiting. Generally the terminal emulator should\n     * be in control of whether the cursor blinks or not and the application in\n     * modern terminals. Note that DECRQM works regardless of this option.\n     */\n    allowSetCursorBlink?: boolean;\n  }\n\n  /**\n   * Enable certain optional VT extensions.\n   */\n  export interface IVtExtensions {\n    /**\n     * Whether the [kitty keyboard protocol][0] (`CSI =|?|>|< u`) is enabled.\n     * When enabled, the terminal will respond to keyboard protocol queries and\n     * allow programs to enable enhanced keyboard reporting. The default is\n     * false.\n     *\n     * [0]: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n     */\n    kittyKeyboard?: boolean;\n\n    /**\n     * Whether [SGR 221 (not bold) and SGR 222 (not faint) are enabled][0].\n     * These are kitty extensions that allow resetting bold and faint\n     * independently. The default is true.\n     *\n     * [0]: https://sw.kovidgoyal.net/kitty/misc-protocol/\n     */\n    kittySgrBoldFaintControl?: boolean;\n\n    /**\n     * Whether [win32-input-mode][0] (`DECSET 9001`) is enabled. When enabled,\n     * the terminal will allow programs to enable win32 INPUT_RECORD  keyboard\n     * reporting via `CSI ? 9001 h`. The default is false.\n     *\n     * [0]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md\n     */\n    win32InputMode?: boolean;\n\n    /**\n     * Whether [color scheme query and notification][0] (`CSI ? 996 n` and\n     * `DECSET 2031`) is enabled. When enabled, the terminal will respond to\n     * color scheme queries with `CSI ? 997 ; 1 n` (dark) or `CSI ? 997 ; 2 n`\n     * (light) based on the relative luminance of the background and foreground\n     * theme colors. Programs can enable unsolicited notifications via\n     * `CSI ? 2031 h`. The default is true.\n     *\n     * [0]: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/\n     */\n    colorSchemeQuery?: boolean;\n  }\n\n  /**\n   * Pty information for Windows.\n   */\n  export interface IWindowsPty {\n    /**\n     * What pty emulation backend is being used.\n     */\n    backend?: 'conpty' | 'winpty';\n    /**\n     * The Windows build version (eg. 19045)\n     */\n    buildNumber?: number;\n  }\n\n  /**\n   * A replacement logger for `console`.\n   */\n  export interface ILogger {\n    /**\n     * Log a trace message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to trace.\n     */\n    trace(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to debug or below.\n     */\n    debug(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to info or below.\n     */\n    info(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to warn or below.\n     */\n    warn(message: string, ...args: any[]): void;\n    /**\n     * Log a debug message, this will only be called if\n     * {@link ITerminalOptions.logLevel} is set to error or below.\n     */\n    error(message: string | Error, ...args: any[]): void;\n  }\n\n  /**\n   * An object that can be disposed via a dispose function.\n   */\n  export interface IDisposable {\n    dispose(): void;\n  }\n\n  /**\n   * An event that can be listened to.\n   * @returns an `IDisposable` to stop listening.\n   */\n  export interface IEvent<T, U = void> {\n    (listener: (arg1: T, arg2: U) => any): IDisposable;\n  }\n\n  /**\n   * Represents a specific line in the terminal that is tracked when scrollback\n   * is trimmed and lines are added or removed. This is a single line that may\n   * be part of a larger wrapped line.\n   */\n  export interface IMarker extends IDisposableWithEvent {\n    /**\n     * A unique identifier for this marker.\n     */\n    readonly id: number;\n\n    /**\n     * The actual line index in the buffer at this point in time. This is set to\n     * -1 if the marker has been disposed.\n     */\n    readonly line: number;\n  }\n\n  /**\n   * Represents a disposable that tracks is disposed state.\n   */\n  export interface IDisposableWithEvent extends IDisposable {\n    /**\n     * Event listener to get notified when this gets disposed.\n     */\n    onDispose: IEvent<void>;\n\n    /**\n     * Whether this is disposed.\n     */\n    readonly isDisposed: boolean;\n  }\n\n  /**\n   * Represents a decoration in the terminal that is associated with a\n   * particular marker and DOM element.\n   */\n  export interface IDecoration extends IDisposableWithEvent {\n    /*\n     * The marker for the decoration in the terminal.\n     */\n    readonly marker: IMarker;\n\n    /**\n     * An event fired when the decoration\n     * is rendered, returns the dom element\n     * associated with the decoration.\n     */\n    readonly onRender: IEvent<HTMLElement>;\n\n    /**\n     * The element that the decoration is rendered to. This will be undefined\n     * until it is rendered for the first time by {@link IDecoration.onRender}.\n     * that.\n     */\n    element: HTMLElement | undefined;\n\n    /**\n     * The options for the overview ruler that can be updated. This will only\n     * take effect when {@link IDecorationOptions.overviewRulerOptions} were\n     * provided initially.\n     */\n    options: Pick<IDecorationOptions, 'overviewRulerOptions'>;\n  }\n\n\n  /**\n   * Overview ruler decoration options\n   */\n  interface IDecorationOverviewRulerOptions {\n    color: string;\n    position?: 'left' | 'center' | 'right' | 'full';\n  }\n\n  /*\n   * Options that define the presentation of the decoration.\n   */\n  export interface IDecorationOptions {\n    /**\n     * The line in the terminal where\n     * the decoration will be displayed\n     */\n    readonly marker: IMarker;\n\n    /*\n     * Where the decoration will be anchored -\n     * defaults to the left edge\n     */\n    readonly anchor?: 'right' | 'left';\n\n    /**\n     * The x position offset relative to the anchor\n     */\n    readonly x?: number;\n\n\n    /**\n     * The width of the decoration in cells, defaults to 1.\n     */\n    readonly width?: number;\n\n    /**\n     * The height of the decoration in cells, defaults to 1.\n     */\n    readonly height?: number;\n\n    /**\n     * The background color of the cell(s). When 2 decorations both set the\n     * foreground color the last registered decoration will be used. Only the\n     * `#RRGGBB` format is supported.\n     */\n    readonly backgroundColor?: string;\n\n    /**\n     * The foreground color of the cell(s). When 2 decorations both set the\n     * foreground color the last registered decoration will be used. Only the\n     * `#RRGGBB` format is supported.\n     */\n    readonly foregroundColor?: string;\n\n    /**\n     * What layer to render the decoration at when {@link backgroundColor} or\n     * {@link foregroundColor} are used. `'bottom'` will render under the\n     * selection, `'top`' will render above the selection\\*.\n     */\n    readonly layer?: 'bottom' | 'top';\n\n    /**\n     * When defined, renders the decoration in the overview ruler to the right\n     * of the terminal. {@link IScrollbarOptions.width} must be set in order to\n     * see the overview ruler.\n     * @param color The color of the decoration.\n     * @param position The position of the decoration.\n     */\n    overviewRulerOptions?: IDecorationOverviewRulerOptions;\n  }\n\n  /**\n   * The set of localizable strings.\n   */\n  export interface ILocalizableStrings {\n    /**\n     * The aria label for the underlying input textarea for the terminal.\n     */\n    promptLabel: string;\n\n    /**\n     * Announcement for when line reading is suppressed due to too many lines\n     * being printed to the terminal when `screenReaderMode` is enabled.\n     */\n    tooMuchOutput: string;\n  }\n\n  /**\n   * Options for configuring the overview ruler rendered beside the scrollbar.\n   */\n  export interface IOverviewRulerOptions {\n    /**\n     * Whether to show the top border of the overview ruler, which uses the\n     * {@link ITheme.overviewRulerBorder} color.\n     */\n    showTopBorder?: boolean;\n\n    /**\n     * Whether to show the bottom border of the overview ruler, which uses the\n     * {@link ITheme.overviewRulerBorder} color.\n     */\n    showBottomBorder?: boolean;\n  }\n\n  /**\n   * Options for configuring the scrollbar.\n   */\n  export interface IScrollbarOptions {\n    /**\n     * Whether to show the scrollbar. When false, this supersedes\n     * {@link IScrollbarOptions.width}. Defaults to true.\n     */\n    showScrollbar?: boolean;\n    /**\n     * Whether to show arrows at the top and bottom of the scrollbar. Defaults\n     * to false.\n     */\n    showArrows?: boolean;\n\n    /**\n     * The width of the scrollbar and overview ruler in CSS pixels. When set,\n     * this enables the overview ruler.\n     */\n    width?: number;\n\n    /**\n     * Controls the visibility and style of the overview ruler which visualizes\n     * decorations underneath the scroll bar.\n     */\n    overviewRuler?: IOverviewRulerOptions;\n  }\n\n  /**\n   * Enable various window manipulation and report features\n   * (`CSI Ps ; Ps ; Ps t`).\n   *\n   * Most settings have no default implementation, as they heavily rely on\n   * the embedding environment.\n   *\n   * To implement a feature, create a custom CSI hook like this:\n   * ```ts\n   * term.parser.addCsiHandler({final: 't'}, params => {\n   *   const ps = params[0];\n   *   switch (ps) {\n   *     case XY:\n   *       ...            // your implementation for option XY\n   *       return true;   // signal Ps=XY was handled\n   *   }\n   *   return false;      // any Ps that was not handled\n   * });\n   * ```\n   *\n   * Note on security:\n   * Most features are meant to deal with some information of the host machine\n   * where the terminal runs on. This is seen as a security risk possibly\n   * leaking sensitive data of the host to the program in the terminal.\n   * Therefore all options (even those without a default implementation) are\n   * guarded by the boolean flag and disabled by default.\n   */\n  export interface IWindowOptions {\n    /**\n     * Ps=1    De-iconify window.\n     * No default implementation.\n     */\n    restoreWin?: boolean;\n    /**\n     * Ps=2    Iconify window.\n     * No default implementation.\n     */\n    minimizeWin?: boolean;\n    /**\n     * Ps=3 ; x ; y\n     * Move window to [x, y].\n     * No default implementation.\n     */\n    setWinPosition?: boolean;\n    /**\n     * Ps = 4 ; height ; width\n     * Resize the window to given `height` and `width` in pixels.\n     * Omitted parameters should reuse the current height or width.\n     * Zero parameters should use the display's height or width.\n     * No default implementation.\n     */\n    setWinSizePixels?: boolean;\n    /**\n     * Ps=5    Raise the window to the front of the stacking order.\n     * No default implementation.\n     */\n    raiseWin?: boolean;\n    /**\n     * Ps=6    Lower the xterm window to the bottom of the stacking order.\n     * No default implementation.\n     */\n    lowerWin?: boolean;\n    /** Ps=7    Refresh the window. */\n    refreshWin?: boolean;\n    /**\n     * Ps = 8 ; height ; width\n     * Resize the text area to given height and width in characters.\n     * Omitted parameters should reuse the current height or width.\n     * Zero parameters use the display's height or width.\n     * No default implementation.\n     */\n    setWinSizeChars?: boolean;\n    /**\n     * Ps=9 ; 0   Restore maximized window.\n     * Ps=9 ; 1   Maximize window (i.e., resize to screen size).\n     * Ps=9 ; 2   Maximize window vertically.\n     * Ps=9 ; 3   Maximize window horizontally.\n     * No default implementation.\n     */\n    maximizeWin?: boolean;\n    /**\n     * Ps=10 ; 0  Undo full-screen mode.\n     * Ps=10 ; 1  Change to full-screen.\n     * Ps=10 ; 2  Toggle full-screen.\n     * No default implementation.\n     */\n    fullscreenWin?: boolean;\n    /** Ps=11   Report xterm window state.\n     * If the xterm window is non-iconified, it returns \"CSI 1 t\".\n     * If the xterm window is iconified, it returns \"CSI 2 t\".\n     * No default implementation.\n     */\n    getWinState?: boolean;\n    /**\n     * Ps=13      Report xterm window position. Result is \"CSI 3 ; x ; y t\".\n     * Ps=13 ; 2  Report xterm text-area position. Result is \"CSI 3 ; x ; y t\".\n     * No default implementation.\n     */\n    getWinPosition?: boolean;\n    /**\n     * Ps=14      Report xterm text area size in pixels. Result is \"CSI 4 ; height ; width t\".\n     * Ps=14 ; 2  Report xterm window size in pixels. Result is \"CSI  4 ; height ; width t\".\n     * Has a default implementation.\n     */\n    getWinSizePixels?: boolean;\n    /**\n     * Ps=15    Report size of the screen in pixels. Result is \"CSI 5 ; height ; width t\".\n     * No default implementation.\n     */\n    getScreenSizePixels?: boolean;\n    /**\n     * Ps=16  Report xterm character cell size in pixels. Result is \"CSI 6 ; height ; width t\".\n     * Has a default implementation.\n     */\n    getCellSizePixels?: boolean;\n    /**\n     * Ps=18  Report the size of the text area in characters. Result is \"CSI 8 ; height ; width t\".\n     * Has a default implementation.\n     */\n    getWinSizeChars?: boolean;\n    /**\n     * Ps=19  Report the size of the screen in characters. Result is \"CSI 9 ; height ; width t\".\n     * No default implementation.\n     */\n    getScreenSizeChars?: boolean;\n    /**\n     * Ps=20  Report xterm window's icon label. Result is \"OSC L label ST\".\n     * No default implementation.\n     */\n    getIconTitle?: boolean;\n    /**\n     * Ps=21  Report xterm window's title. Result is \"OSC l label ST\".\n     * No default implementation.\n     */\n    getWinTitle?: boolean;\n    /**\n     * Ps=22 ; 0  Save xterm icon and window title on stack.\n     * Ps=22 ; 1  Save xterm icon title on stack.\n     * Ps=22 ; 2  Save xterm window title on stack.\n     * All variants have a default implementation.\n     */\n    pushTitle?: boolean;\n    /**\n     * Ps=23 ; 0  Restore xterm icon and window title from stack.\n     * Ps=23 ; 1  Restore xterm icon title from stack.\n     * Ps=23 ; 2  Restore xterm window title from stack.\n     * All variants have a default implementation.\n     */\n    popTitle?: boolean;\n    /**\n     * Ps>=24  Resize to Ps lines (DECSLPP).\n     * DECSLPP is not implemented. This settings is also used to\n     * enable / disable DECCOLM (earlier variant of DECSLPP).\n     */\n    setWinLines?: boolean;\n  }\n\n  /**\n   * The class that represents an xterm.js terminal.\n   */\n  export class Terminal implements IDisposable {\n    /**\n     * The element containing the terminal.\n     */\n    readonly element: HTMLElement | undefined;\n\n    /**\n     * The screen element containing the terminal's canvas rendering layers and\n     * decorations, excluding the viewport and the scrollbar.\n     */\n    readonly screenElement: HTMLElement | undefined;\n\n    /**\n     * The textarea that accepts input for the terminal.\n     */\n    readonly textarea: HTMLTextAreaElement | undefined;\n\n    /**\n     * The number of rows in the terminal's viewport. Use\n     * `ITerminalOptions.rows` to set this in the constructor and\n     * `Terminal.resize` for when the terminal exists.\n     */\n    readonly rows: number;\n\n    /**\n     * The number of columns in the terminal's viewport. Use\n     * `ITerminalOptions.cols` to set this in the constructor and\n     * `Terminal.resize` for when the terminal exists.\n     */\n    readonly cols: number;\n\n    /**\n     * Access to the terminal's normal and alt buffer.\n     */\n    readonly buffer: IBufferNamespace;\n\n    /**\n     * Get all markers registered against the buffer. If the alt buffer is\n     * active this will always return [].\n     */\n    readonly markers: ReadonlyArray<IMarker>;\n\n    /**\n     * Get the parser interface to register custom escape sequence handlers.\n     */\n    readonly parser: IParser;\n\n    /**\n     * (EXPERIMENTAL) Get the Unicode handling interface to register and switch\n     * Unicode version.\n     */\n    readonly unicode: IUnicodeHandling;\n\n    /**\n     * Gets the terminal modes as set by SM/DECSET.\n     */\n    readonly modes: IModes;\n\n    /**\n     * The dimensions of the terminal. This will be undefined before\n     * {@link open} is called.\n     */\n    readonly dimensions: IRenderDimensions | undefined;\n\n    /**\n     * Gets or sets the terminal options. This supports setting multiple\n     * options.\n     *\n     * @example Get a single option\n     * ```ts\n     * console.log(terminal.options.fontSize);\n     * ```\n     *\n     * @example Set a single option:\n     * ```ts\n     * terminal.options.fontSize = 12;\n     * ```\n     * Note that for options that are object, a new object must be used in order\n     * to take effect as a reference comparison will be done:\n     * ```ts\n     * const newValue = terminal.options.theme;\n     * newValue.background = '#000000';\n     *\n     * // This won't work\n     * terminal.options.theme = newValue;\n     *\n     * // This will work\n     * terminal.options.theme = { ...newValue };\n     * ```\n     *\n     * @example Set multiple options\n     * ```ts\n     * terminal.options = {\n     *   fontSize: 12,\n     *   fontFamily: 'Courier New'\n     * };\n     * ```\n     */\n    options: ITerminalOptions;\n\n    /**\n     * Natural language strings that can be localized.\n     */\n    static strings: ILocalizableStrings;\n\n    /**\n     * Creates a new `Terminal` object.\n     *\n     * @param options An object containing a set of options.\n     */\n    constructor(options?: ITerminalOptions & ITerminalInitOnlyOptions);\n\n    /**\n     * Adds an event listener for when the bell is triggered.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onBell: IEvent<void>;\n\n    /**\n     * Adds an event listener for when a binary event fires. This is used to\n     * enable non UTF-8 conformant binary messages to be sent to the backend.\n     * Currently this is only used for a certain type of mouse reports that\n     * happen to be not UTF-8 compatible.\n     * The event value is a JS string, pass it to the underlying pty as\n     * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onBinary: IEvent<string>;\n\n    /**\n     * Adds an event listener for the cursor moves.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onCursorMove: IEvent<void>;\n\n    /**\n     * Adds an event listener for when a data event fires. This happens for\n     * example when the user types or pastes into the terminal. The event value\n     * is whatever `string` results, in a typical setup, this should be passed\n     * on to the backing pty.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onData: IEvent<string>;\n\n    /**\n     * Adds an event listener for when a key is pressed. The event value\n     * contains the string that will be sent in the data event as well as the\n     * DOM event that triggered it.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;\n\n    /**\n     * Adds an event listener for when a line feed is added.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onLineFeed: IEvent<void>;\n\n    /**\n     * Adds an event listener for when rows are rendered. The event value\n     * contains the start row and end rows of the rendered area (ranges from `0`\n     * to `Terminal.rows - 1`).\n     * @returns an `IDisposable` to stop listening.\n     */\n    onRender: IEvent<{ start: number, end: number }>;\n\n    /**\n     * Adds an event listener for when data has been parsed by the terminal,\n     * after {@link write} is called. This event is useful to listen for any\n     * changes in the buffer.\n     *\n     * This fires at most once per frame, after data parsing completes. Note\n     * that this can fire when there are still writes pending if there is a lot\n     * of data.\n     */\n    onWriteParsed: IEvent<void>;\n\n    /**\n     * Adds an event listener for when the terminal is resized. The event value\n     * contains the new size.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onResize: IEvent<{ cols: number, rows: number }>;\n\n    /**\n     * Adds an event listener for when a scroll occurs. The event value is the\n     * new position of the viewport.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onScroll: IEvent<number>;\n\n    /**\n     * Adds an event listener for when a selection change occurs.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onSelectionChange: IEvent<void>;\n\n    /**\n     * Adds an event listener for when an OSC 0 or OSC 2 title change occurs.\n     * The event value is the new title.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onTitleChange: IEvent<string>;\n\n    /**\n     * Adds an event listener for when the terminal's dimensions change.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onDimensionsChange: IEvent<IRenderDimensions>;\n\n    /**\n     * Unfocus the terminal.\n     */\n    blur(): void;\n\n    /**\n     * Focus the terminal.\n     */\n    focus(): void;\n\n    /**\n     * Input data to application side. The data is treated the same way input\n     * typed into the terminal would (ie. the {@link onData} event will fire).\n     * @param data The data to forward to the application.\n     * @param wasUserInput Whether the input is genuine user input. This is true\n     * by default and triggers additionalbehavior like focus or selection\n     * clearing. Set this to false if the data sent should not be treated like\n     * user input would, for example passing an escape sequence to the\n     * application.\n     */\n    input(data: string, wasUserInput?: boolean): void;\n\n    /**\n     * Resizes the terminal. It's best practice to debounce calls to resize,\n     * this will help ensure that the pty can respond to the resize event\n     * before another one occurs.\n     * @param x The number of columns to resize to.\n     * @param y The number of rows to resize to.\n     */\n    resize(columns: number, rows: number): void;\n\n    /**\n     * Opens the terminal within an element. This should also be called if the\n     * xterm.js element ever changes browser window.\n     * @param parent The element to create the terminal within. This element\n     * must be visible (have dimensions) when `open` is called as several DOM-\n     * based measurements need to be performed when this function is called.\n     */\n    open(parent: HTMLElement): void;\n\n    /**\n     * Attaches a custom key event handler which is run before keys are\n     * processed, giving consumers of xterm.js ultimate control as to what keys\n     * should be processed by the terminal and what keys should not.\n     * @param customKeyEventHandler The custom KeyboardEvent handler to attach.\n     * This is a function that takes a KeyboardEvent, allowing consumers to stop\n     * propagation and/or prevent the default action. The function returns\n     * whether the event should be processed by xterm.js.\n     *\n     * @example A custom keymap that overrides the backspace key\n     * ```ts\n     * const keymap = [\n     *   { \"key\": \"Backspace\", \"shiftKey\": false, \"mapCode\": 8 },\n     *   { \"key\": \"Backspace\", \"shiftKey\": true, \"mapCode\": 127 }\n     * ];\n     * term.attachCustomKeyEventHandler(ev => {\n     *   if (ev.type === 'keydown') {\n     *     for (let i in keymap) {\n     *       if (keymap[i].key == ev.key && keymap[i].shiftKey == ev.shiftKey) {\n     *         socket.send(String.fromCharCode(keymap[i].mapCode));\n     *         return false;\n     *       }\n     *     }\n     *   }\n     * });\n     * ```\n     */\n    attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;\n\n    /**\n     * Attaches a custom wheel event handler which is run before keys are\n     * processed, giving consumers of xterm.js control over whether to proceed\n     * or cancel terminal wheel events.\n     * @param customWheelEventHandler The custom WheelEvent handler to attach.\n     * This is a function that takes a WheelEvent, allowing consumers to stop\n     * propagation and/or prevent the default action. The function returns\n     * whether the event should be processed by xterm.js.\n     *\n     * @example A handler that prevents all wheel events while ctrl is held from\n     * being processed.\n     * ```ts\n     * term.attachCustomWheelEventHandler(ev => {\n     *   if (ev.ctrlKey) {\n     *     return false;\n     *   }\n     *   return true;\n     * });\n     * ```\n     */\n    attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void;\n\n    /**\n     * Registers a link provider, allowing a custom parser to be used to match\n     * and handle links. Multiple link providers can be used, they will be asked\n     * in the order in which they are registered.\n     * @param linkProvider The link provider to use to detect links.\n     */\n    registerLinkProvider(linkProvider: ILinkProvider): IDisposable;\n\n    /**\n     * Registers a character joiner, allowing custom sequences of characters to\n     * be rendered as a single unit. This is useful in particular for rendering\n     * ligatures and graphemes, among other things.\n     *\n     * Each registered character joiner is called with a string of text\n     * representing a portion of a line in the terminal that can be rendered as\n     * a single unit. The joiner must return a sorted array, where each entry is\n     * itself an array of length two, containing the start (inclusive) and end\n     * (exclusive) index of a substring of the input that should be rendered as\n     * a single unit. When multiple joiners are provided, the results of each\n     * are collected. If there are any overlapping substrings between them, they\n     * are combined into one larger unit that is drawn together.\n     *\n     * All character joiners that are registered get called every time a line is\n     * rendered in the terminal, so it is essential for the handler function to\n     * run as quickly as possible to avoid slowdowns when rendering. Similarly,\n     * joiners should strive to return the smallest possible substrings to\n     * render together, since they aren't drawn as optimally as individual\n     * characters.\n     *\n     * @param handler The function that determines character joins. It is called\n     * with a string of text that is eligible for joining and returns an array\n     * where each entry is an array containing the start (inclusive) and end\n     * (exclusive) indexes of ranges that should be rendered as a single unit.\n     * @returns The ID of the new joiner, this can be used to deregister\n     */\n    registerCharacterJoiner(handler: (text: string) => [number, number][]): number;\n\n    /**\n     * Deregisters the character joiner if one was registered. Note that\n     * character joiners are only used by the webgl renderer.\n     * @param joinerId The character joiner's ID (returned after register)\n     */\n    deregisterCharacterJoiner(joinerId: number): void;\n\n    /**\n     * Adds a marker to the normal buffer and returns it.\n     * @param cursorYOffset The y position offset of the marker from the cursor.\n     * @returns The new marker or undefined.\n     */\n    registerMarker(cursorYOffset?: number): IMarker;\n\n    /**\n     * Registers a decoration to the terminal.\n     * @param decorationOptions, which takes a marker and an optional anchor,\n     * width, height, and x offset from the anchor. Returns the decoration or\n     * undefined if the alt buffer is active or the marker has already been\n     * disposed of.\n     * @throws when options include a negative x offset.\n     */\n    registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;\n\n    /**\n     * Gets whether the terminal has an active selection.\n     */\n    hasSelection(): boolean;\n\n    /**\n     * Gets the terminal's current selection, this is useful for implementing\n     * copy behavior outside of xterm.js.\n     */\n    getSelection(): string;\n\n    /**\n     * Gets the selection position or undefined if there is no selection.\n     */\n    getSelectionPosition(): IBufferRange | undefined;\n\n    /**\n     * Clears the current terminal selection.\n     */\n    clearSelection(): void;\n\n    /**\n     * Selects text within the terminal.\n     * @param column The column the selection starts at.\n     * @param row The row the selection starts at.\n     * @param length The length of the selection.\n     */\n    select(column: number, row: number, length: number): void;\n\n    /**\n     * Selects all text within the terminal.\n     */\n    selectAll(): void;\n\n    /**\n     * Selects text in the buffer between 2 lines.\n     * @param start The 0-based line index to select from (inclusive).\n     * @param end The 0-based line index to select to (inclusive).\n     */\n    selectLines(start: number, end: number): void;\n\n    /*\n     * Disposes of the terminal, detaching it from the DOM and removing any\n     * active listeners. Once the terminal is disposed it should not be used\n     * again.\n     */\n    dispose(): void;\n\n    /**\n     * Scroll the display of the terminal\n     * @param amount The number of lines to scroll down (negative scroll up).\n     */\n    scrollLines(amount: number): void;\n\n    /**\n     * Scroll the display of the terminal by a number of pages.\n     * @param pageCount The number of pages to scroll (negative scrolls up).\n     */\n    scrollPages(pageCount: number): void;\n\n    /**\n     * Scrolls the display of the terminal to the top.\n     */\n    scrollToTop(): void;\n\n    /**\n     * Scrolls the display of the terminal to the bottom.\n     */\n    scrollToBottom(): void;\n\n    /**\n     * Scrolls to a line within the buffer.\n     * @param line The 0-based line index to scroll to.\n     */\n    scrollToLine(line: number): void;\n\n    /**\n     * Clear the entire buffer, making the prompt line the new first line.\n     */\n    clear(): void;\n\n    /**\n     * Write data to the terminal.\n     *\n     * Note that the change will not be reflected in the {@link buffer}\n     * immediately as the data is processed asynchronously. Provide a\n     * {@link callback} to know when the data was processed.\n     * @param data The data to write to the terminal. This can either be raw\n     * bytes given as Uint8Array from the pty or a string. Raw bytes will always\n     * be treated as UTF-8 encoded, string data as UTF-16.\n     * @param callback Optional callback that fires when the data was processed\n     * by the parser. This callback must be provided and awaited in order for\n     * {@link buffer} to reflect the change in the write.\n     */\n    write(data: string | Uint8Array, callback?: () => void): void;\n\n    /**\n     * Writes data to the terminal, followed by a break line character (\\n).\n     *\n     * Note that the change will not be reflected in the {@link buffer}\n     * immediately as the data is processed asynchronously. Provide a\n     * {@link callback} to know when the data was processed.\n     * @param data The data to write to the terminal. This can either be raw\n     * bytes given as Uint8Array from the pty or a string. Raw bytes will always\n     * be treated as UTF-8 encoded, string data as UTF-16.\n     * @param callback Optional callback that fires when the data was processed\n     * by the parser. This callback must be provided and awaited in order for\n     * {@link buffer} to reflect the change in the write.\n     */\n    writeln(data: string | Uint8Array, callback?: () => void): void;\n\n    /**\n     * Writes text to the terminal, performing the necessary transformations for\n     * pasted text.\n     * @param data The text to write to the terminal.\n     */\n    paste(data: string): void;\n\n    /**\n     * Tells the renderer to refresh terminal content between two rows\n     * (inclusive) at the next opportunity.\n     * @param start The row to start from (between 0 and this.rows - 1).\n     * @param end The row to end at (between start and this.rows - 1).\n     */\n    refresh(start: number, end: number): void;\n\n    /**\n     * Clears the texture atlas of the webgl renderer if it's active. Doing\n     * this will force a redraw of all glyphs which can workaround issues\n     * causing the texture to become corrupt, for example Chromium/Nvidia has an\n     * issue where the texture gets messed up when resuming the OS from sleep.\n     */\n    clearTextureAtlas(): void;\n\n    /**\n     * Perform a full reset (RIS, aka '\\x1bc').\n     */\n    reset(): void;\n\n    /**\n     * Loads an addon into this instance of xterm.js.\n     * @param addon The addon to load.\n     */\n    loadAddon(addon: ITerminalAddon): void;\n  }\n\n  /**\n   * An addon that can provide additional functionality to the terminal.\n   */\n  export interface ITerminalAddon extends IDisposable {\n    /**\n     * This is called when the addon is activated.\n     */\n    activate(terminal: Terminal): void;\n  }\n\n  /**\n   * An object representing a range within the viewport of the terminal.\n   */\n  export interface IViewportRange {\n    /**\n     * The start of the range.\n     */\n    start: IViewportRangePosition;\n\n    /**\n     * The end of the range.\n     */\n    end: IViewportRangePosition;\n  }\n\n  /**\n   * An object representing a cell position within the viewport of the terminal.\n   */\n  interface IViewportRangePosition {\n    /**\n     * The x position of the cell. This is a 0-based index that refers to the\n     * space in between columns, not the column itself. Index 0 refers to the\n     * left side of the viewport, index `Terminal.cols` refers to the right side\n     * of the viewport. This can be thought of as how a cursor is positioned in\n     * a text editor.\n     */\n    x: number;\n\n    /**\n     * The y position of the cell. This is a 0-based index that refers to a\n     * specific row.\n     */\n    y: number;\n  }\n\n  /**\n   * A link handler for OSC 8 hyperlinks.\n   */\n  interface ILinkHandler {\n    /**\n     * Calls when the link is activated.\n     * @param event The mouse event triggering the callback.\n     * @param text The text of the link.\n     * @param range The buffer range of the link.\n     */\n    activate(event: MouseEvent, text: string, range: IBufferRange): void;\n\n    /**\n     * Called when the mouse hovers the link. To use this to create a DOM-based\n     * hover tooltip, create the hover element within `Terminal.element` and\n     * add the `xterm-hover` class to it, that will cause mouse events to not\n     * fall through and activate other links.\n     * @param event The mouse event triggering the callback.\n     * @param text The text of the link.\n     * @param range The buffer range of the link.\n     */\n    hover?(event: MouseEvent, text: string, range: IBufferRange): void;\n\n    /**\n     * Called when the mouse leaves the link.\n     * @param event The mouse event triggering the callback.\n     * @param text The text of the link.\n     * @param range The buffer range of the link.\n     */\n    leave?(event: MouseEvent, text: string, range: IBufferRange): void;\n\n    /**\n     * Whether to receive non-HTTP URLs from LinkProvider. When false, any\n     * usage of non-HTTP URLs will be ignored. Enabling this option without\n     * proper protection in `activate` function may cause security issues such\n     * as XSS.\n     */\n    allowNonHttpProtocols?: boolean;\n  }\n\n  /**\n   * A custom link provider.\n   */\n  interface ILinkProvider {\n    /**\n     * Provides a link a buffer position\n     * @param bufferLineNumber The y position of the buffer to check for links\n     * within.\n     * @param callback The callback to be fired when ready with the resulting\n     * link(s) for the line or `undefined`.\n     */\n    provideLinks(bufferLineNumber: number, callback: (links: ILink[] | undefined) => void): void;\n  }\n\n  /**\n   * A link within the terminal.\n   */\n  interface ILink {\n    /**\n     * The buffer range of the link.\n     */\n    range: IBufferRange;\n\n    /**\n     * The text of the link.\n     */\n    text: string;\n\n    /**\n     * What link decorations to show when hovering the link, this property is\n     * tracked and changes made after the link is provided will trigger changes.\n     * If not set, all decroations will be enabled.\n     */\n    decorations?: ILinkDecorations;\n\n    /**\n     * Calls when the link is activated.\n     * @param event The mouse event triggering the callback.\n     * @param text The text of the link.\n     */\n    activate(event: MouseEvent, text: string): void;\n\n    /**\n     * Called when the mouse hovers the link. To use this to create a DOM-based\n     * hover tooltip, create the hover element within `Terminal.element` and add\n     * the `xterm-hover` class to it, that will cause mouse events to not fall\n     * through and activate other links.\n     * @param event The mouse event triggering the callback.\n     * @param text The text of the link.\n     */\n    hover?(event: MouseEvent, text: string): void;\n\n    /**\n     * Called when the mouse leaves the link.\n     * @param event The mouse event triggering the callback.\n     * @param text The text of the link.\n     */\n    leave?(event: MouseEvent, text: string): void;\n\n    /**\n     * Called when the link is released and no longer used by xterm.js.\n     */\n    dispose?(): void;\n  }\n\n  /**\n   * A set of decorations that can be applied to links.\n   */\n  interface ILinkDecorations {\n    /**\n     * Whether the cursor is set to pointer.\n     */\n    pointerCursor: boolean;\n\n    /**\n     * Whether the underline is visible\n     */\n    underline: boolean;\n  }\n\n  /**\n   * A range within a buffer.\n   */\n  interface IBufferRange {\n    /**\n     * The start position of the range.\n     */\n    start: IBufferCellPosition;\n\n    /**\n     * The end position of the range.\n     */\n    end: IBufferCellPosition;\n  }\n\n  /**\n   * A position within a buffer.\n   */\n  interface IBufferCellPosition {\n    /**\n     * The x position within the buffer (1-based).\n     */\n    x: number;\n\n    /**\n     * The y position within the buffer (1-based).\n     */\n    y: number;\n  }\n\n  /**\n   * Represents a terminal buffer.\n   */\n  interface IBuffer {\n    /**\n     * The type of the buffer.\n     */\n    readonly type: 'normal' | 'alternate';\n\n    /**\n     * The y position of the cursor. This ranges between `0` (when the\n     * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the\n     * last row).\n     */\n    readonly cursorY: number;\n\n    /**\n     * The x position of the cursor. This ranges between `0` (left side) and\n     * `Terminal.cols` (after last cell of the row).\n     */\n    readonly cursorX: number;\n\n    /**\n     * The line within the buffer where the top of the viewport is.\n     */\n    readonly viewportY: number;\n\n    /**\n     * The line within the buffer where the top of the bottom page is (when\n     * fully scrolled down).\n     */\n    readonly baseY: number;\n\n    /**\n     * The amount of lines in the buffer.\n     */\n    readonly length: number;\n\n    /**\n     * Gets a line from the buffer, or undefined if the line index does not\n     * exist.\n     *\n     * Note that the result of this function should be used immediately after\n     * calling as when the terminal updates it could lead to unexpected\n     * behavior.\n     *\n     * @param y The line index to get.\n     */\n    getLine(y: number): IBufferLine | undefined;\n\n    /**\n     * Creates an empty cell object suitable as a cell reference in\n     * `line.getCell(x, cell)`. Use this to avoid costly recreation of\n     * cell objects when dealing with tons of cells.\n     */\n    getNullCell(): IBufferCell;\n  }\n\n  export interface IBufferElementProvider {\n    /**\n     * Provides a document fragment or HTMLElement containing the buffer\n     * elements.\n     */\n    provideBufferElements(): DocumentFragment | HTMLElement;\n  }\n\n  /**\n   * Represents the terminal's set of buffers.\n   */\n  interface IBufferNamespace {\n    /**\n     * The active buffer, this will either be the normal or alternate buffers.\n     */\n    readonly active: IBuffer;\n\n    /**\n     * The normal buffer.\n     */\n    readonly normal: IBuffer;\n\n    /**\n     * The alternate buffer, this becomes the active buffer when an application\n     * enters this mode via DECSET (`CSI ? 4 7 h`)\n     */\n    readonly alternate: IBuffer;\n\n    /**\n     * Adds an event listener for when the active buffer changes.\n     * @returns an `IDisposable` to stop listening.\n     */\n    onBufferChange: IEvent<IBuffer>;\n  }\n\n  /**\n   * Represents a line in the terminal's buffer.\n   */\n  interface IBufferLine {\n    /**\n     * Whether the line is wrapped from the previous line.\n     */\n    readonly isWrapped: boolean;\n\n    /**\n     * The length of the line, all call to getCell beyond the length will result\n     * in `undefined`. Note that this may exceed columns as the line array may\n     * not be trimmed after a resize, compare against {@link Terminal.cols} to\n     * get the actual maximum length of a line.\n     */\n    readonly length: number;\n\n    /**\n     * Gets a cell from the line, or undefined if the line index does not exist.\n     *\n     * Note that the result of this function should be used immediately after\n     * calling as when the terminal updates it could lead to unexpected\n     * behavior.\n     *\n     * @param x The character index to get.\n     * @param cell Optional cell object to load data into for performance\n     * reasons. This is mainly useful when every cell in the buffer is being\n     * looped over to avoid creating new objects for every cell.\n     */\n    getCell(x: number, cell?: IBufferCell): IBufferCell | undefined;\n\n    /**\n     * Gets the line as a string. Note that this is gets only the string for the\n     * line, not taking isWrapped into account.\n     *\n     * @param trimRight Whether to trim any whitespace at the right of the line.\n     * @param startColumn The column to start from (inclusive).\n     * @param endColumn The column to end at (exclusive).\n     */\n    translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;\n  }\n\n  /**\n   * Represents a single cell in the terminal's buffer.\n   */\n  interface IBufferCell {\n    /**\n     * The width of the character. Some examples:\n     *\n     * - `1` for most cells.\n     * - `2` for wide character like CJK glyphs.\n     * - `0` for cells immediately following cells with a width of `2`.\n     */\n    getWidth(): number;\n\n    /**\n     * The character(s) within the cell. Examples of what this can contain:\n     *\n     * - A normal width character\n     * - A wide character (eg. CJK)\n     * - An emoji\n     */\n    getChars(): string;\n\n    /**\n     * Gets the UTF32 codepoint of single characters, if content is a combined\n     * string it returns the codepoint of the last character in the string.\n     */\n    getCode(): number;\n\n    /**\n     * Gets the number representation of the foreground color mode, this can be\n     * used to perform quick comparisons of 2 cells to see if they're the same.\n     * Use `isFgRGB`, `isFgPalette` and `isFgDefault` to check what color mode\n     * a cell is.\n     */\n    getFgColorMode(): number;\n\n    /**\n     * Gets the number representation of the background color mode, this can be\n     * used to perform quick comparisons of 2 cells to see if they're the same.\n     * Use `isBgRGB`, `isBgPalette` and `isBgDefault` to check what color mode\n     * a cell is.\n     */\n    getBgColorMode(): number;\n\n    /**\n     * Gets a cell's foreground color number, this differs depending on what the\n     * color mode of the cell is:\n     *\n     * - Default: This should be 0, representing the default foreground color\n     *   (CSI 39 m).\n     * - Palette: This is a number from 0 to 255 of ANSI colors (CSI 3(0-7) m,\n     *   CSI 9(0-7) m, CSI 38 ; 5 ; 0-255 m).\n     * - RGB: A hex value representing a 'true color': 0xRRGGBB.\n     *   (CSI 3 8 ; 2 ; Pi ; Pr ; Pg ; Pb)\n     */\n    getFgColor(): number;\n\n    /**\n     * Gets a cell's background color number, this differs depending on what the\n     * color mode of the cell is:\n     *\n     * - Default: This should be 0, representing the default background color\n     *   (CSI 49 m).\n     * - Palette: This is a number from 0 to 255 of ANSI colors\n     *   (CSI 4(0-7) m, CSI 10(0-7) m, CSI 48 ; 5 ; 0-255 m).\n     * - RGB: A hex value representing a 'true color': 0xRRGGBB\n     *   (CSI 4 8 ; 2 ; Pi ; Pr ; Pg ; Pb)\n     */\n    getBgColor(): number;\n\n    /** Whether the cell has the bold attribute (CSI 1 m). */\n    isBold(): number;\n    /** Whether the cell has the italic attribute (CSI 3 m). */\n    isItalic(): number;\n    /** Whether the cell has the dim attribute (CSI 2 m). */\n    isDim(): number;\n    /** Whether the cell has the underline attribute (CSI 4 m). */\n    isUnderline(): number;\n    /** Whether the cell has the blink attribute (CSI 5 m). */\n    isBlink(): number;\n    /** Whether the cell has the inverse attribute (CSI 7 m). */\n    isInverse(): number;\n    /** Whether the cell has the invisible attribute (CSI 8 m). */\n    isInvisible(): number;\n    /** Whether the cell has the strikethrough attribute (CSI 9 m). */\n    isStrikethrough(): number;\n    /** Whether the cell has the overline attribute (CSI 53 m). */\n    isOverline(): number;\n\n    /** Whether the cell is using the RGB foreground color mode. */\n    isFgRGB(): boolean;\n    /** Whether the cell is using the RGB background color mode. */\n    isBgRGB(): boolean;\n    /** Whether the cell is using the palette foreground color mode. */\n    isFgPalette(): boolean;\n    /** Whether the cell is using the palette background color mode. */\n    isBgPalette(): boolean;\n    /** Whether the cell is using the default foreground color mode. */\n    isFgDefault(): boolean;\n    /** Whether the cell is using the default background color mode. */\n    isBgDefault(): boolean;\n\n    /** Whether the cell has the default attribute (no color or style). */\n    isAttributeDefault(): boolean;\n\n    /** Gets the underline style. */\n    getUnderlineStyle(): number;\n    /** Gets the underline color number. */\n    getUnderlineColor(): number;\n    /** Gets the underline color mode. */\n    getUnderlineColorMode(): number;\n    /** Whether the cell is using the RGB underline color mode. */\n    isUnderlineColorRGB(): boolean;\n    /** Whether the cell is using the palette underline color mode. */\n    isUnderlineColorPalette(): boolean;\n    /** Whether the cell is using the default underline color mode. */\n    isUnderlineColorDefault(): boolean;\n\n    /**\n     * Compares the cell's attributes (colors and styles) with another cell.\n     * This does not compare the cell's content and excludes URL ids and\n     * underline variant offsets.\n     */\n    attributesEquals(other: IBufferCell): boolean;\n  }\n\n  /**\n   * Data type to register a CSI, DCS or ESC callback in the parser\n   * in the form:\n   *    ESC I..I F\n   *    CSI Prefix P..P I..I F\n   *    DCS Prefix P..P I..I F data_bytes ST\n   *\n   * with these rules/restrictions:\n   * - prefix can only be used with CSI and DCS\n   * - only one leading prefix byte is recognized by the parser\n   *   before any other parameter bytes (P..P)\n   * - intermediate bytes are recognized up to 2\n   *\n   * For custom sequences make sure to read ECMA-48 and the resources at\n   * vt100.net to not clash with existing sequences or reserved address space.\n   * General recommendations:\n   * - use private address space (see ECMA-48)\n   * - use max one intermediate byte (technically not limited by the spec,\n   *   in practice there are no sequences with more than one intermediate byte,\n   *   thus parsers might get confused with more intermediates)\n   * - test against other common emulators to check whether they escape/ignore\n   *   the sequence correctly\n   *\n   * Notes: OSC command registration is handled differently (see addOscHandler)\n   *        APC, PM or SOS is currently not supported.\n   */\n  export interface IFunctionIdentifier {\n    /**\n     * Optional prefix byte, must be in range \\x3c .. \\x3f.\n     * Usable in CSI and DCS.\n     */\n    prefix?: string;\n    /**\n     * Optional intermediate bytes, must be in range \\x20 .. \\x2f.\n     * Usable in CSI, DCS and ESC.\n     */\n    intermediates?: string;\n    /**\n     * Final byte, must be in range \\x40 .. \\x7e for CSI and DCS,\n     * \\x30 .. \\x7e for ESC.\n     */\n    final: string;\n  }\n\n  /**\n   * Allows hooking into the parser for custom handling of escape sequences.\n   *\n   * Note on sync vs. async handlers:\n   * xterm.js implements all parser actions with synchronous handlers.\n   * In general custom handlers should also operate in sync mode wherever\n   * possible to keep the parser fast.\n   * Still the exposed interfaces allow to register async handlers by returning\n   * a `Promise<boolean>`. Here the parser will pause input processing until\n   * the promise got resolved or rejected (in-band blocking). This \"full stop\"\n   * on the input chain allows to implement backpressure from a certain async\n   * action while the terminal state will not progress any further from input.\n   * It does not mean that the terminal state will not change at all in between,\n   * as user actions like resize or reset are still processed immediately.\n   * It is an error to assume a stable terminal state while giving back control\n   * in between, e.g. by multiple chained `then` calls.\n   * Downside of an async handler is a rather bad throughput performance,\n   * thus use async handlers only as a last resort or for actions that have\n   * to rely on async interfaces itself.\n   */\n  export interface IParser {\n    /**\n     * Adds a handler for CSI escape sequences.\n     * @param id Specifies the function identifier under which the callback gets\n     * registered, e.g. {final: 'm'} for SGR.\n     * @param callback The function to handle the sequence. The callback is\n     * called with the numerical params. If the sequence has subparams the array\n     * will contain subarrays with their numercial values. Return `true` if the\n     * sequence was handled, `false` if the parser should try a previous\n     * handler. The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise<boolean>): IDisposable;\n\n    /**\n     * Adds a handler for DCS escape sequences.\n     * @param id Specifies the function identifier under which the callback gets\n     * registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.\n     * @param callback The function to handle the sequence. Note that the\n     * function will only be called once if the sequence finished sucessfully.\n     * There is currently no way to intercept smaller data chunks, data chunks\n     * will be stored up until the sequence is finished. Since DCS sequences are\n     * not limited by the amount of data this might impose a problem for big\n     * payloads. Currently xterm.js limits DCS payload to 10 MB which should\n     * give enough room for most use cases. The function gets the payload and\n     * numerical parameters as arguments. Return `true` if the sequence was\n     * handled, `false` if the parser should try a previous handler. The most\n     * recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise<boolean>): IDisposable;\n\n    /**\n     * Adds a handler for ESC escape sequences.\n     * @param id Specifies the function identifier under which the callback gets\n     * registered, e.g. {intermediates: '%' final: 'G'} for default charset\n     * selection.\n     * @param handler The function to handle the sequence.\n     * Return `true` if the sequence was handled, `false` if the parser should\n     * try a previous handler. The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable;\n\n    /**\n     * Adds a handler for OSC escape sequences.\n     * @param ident The number (first parameter) of the sequence.\n     * @param callback The function to handle the sequence. Note that the\n     * function will only be called once if the sequence finished sucessfully.\n     * There is currently no way to intercept smaller data chunks, data chunks\n     * will be stored up until the sequence is finished. Since OSC sequences are\n     * not limited by the amount of data this might impose a problem for big\n     * payloads. Currently xterm.js limits OSC payload to 10 MB which should\n     * give enough room for most use cases. The callback is called with OSC data\n     * string. Return `true` if the sequence was handled, `false` if the parser\n     * should try a previous handler. The most recently added handler is tried\n     * first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;\n\n    /**\n     * Adds a handler for APC escape sequences.\n     * @param ident The identifier (first character) of the sequence as a\n     * character code, e.g. 71 for 'G' (Kitty graphics protocol).\n     * @param callback The function to handle the sequence. Note that the\n     * function will only be called once if the sequence finished successfully.\n     * There is currently no way to intercept smaller data chunks, data chunks\n     * will be stored up until the sequence is finished. Since APC sequences are\n     * not limited by the amount of data this might impose a problem for big\n     * payloads. Currently xterm.js limits APC payload to 10 MB which should\n     * give enough room for most use cases. The callback is called with APC data\n     * string (excluding the identifier character). Return `true` if the\n     * sequence was handled, `false` if the parser should try a previous\n     * handler. The most recently added handler is tried first.\n     * @returns An IDisposable you can call to remove this handler.\n     */\n    registerApcHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;\n  }\n\n  /**\n   * (EXPERIMENTAL) Unicode version provider.\n   * Used to register custom Unicode versions with `Terminal.unicode.register`.\n   */\n  export interface IUnicodeVersionProvider {\n    /**\n     * String indicating the Unicode version provided.\n     */\n    readonly version: string;\n\n    /**\n     * Unicode version dependent wcwidth implementation.\n     */\n    wcwidth(codepoint: number): 0 | 1 | 2;\n    charProperties(codepoint: number, preceding: number): number;\n  }\n\n  /**\n   * (EXPERIMENTAL) Unicode handling interface.\n   */\n  export interface IUnicodeHandling {\n    /**\n     * Register a custom Unicode version provider.\n     */\n    register(provider: IUnicodeVersionProvider): void;\n\n    /**\n     * Registered Unicode versions.\n     */\n    readonly versions: ReadonlyArray<string>;\n\n    /**\n     * Getter/setter for active Unicode version.\n     */\n    activeVersion: string;\n  }\n\n  /**\n   * Terminal modes as set by SM/DECSET.\n   */\n  export interface IModes {\n    /**\n     * Application Cursor Keys (DECCKM): `CSI ? 1 h`\n     */\n    readonly applicationCursorKeysMode: boolean;\n    /**\n     * Application Keypad Mode (DECNKM): `CSI ? 6 6 h`\n     */\n    readonly applicationKeypadMode: boolean;\n    /**\n     * Bracketed Paste Mode: `CSI ? 2 0 0 4 h`\n     */\n    readonly bracketedPasteMode: boolean;\n    /**\n     * Insert Mode (IRM): `CSI 4 h`\n     */\n    readonly insertMode: boolean;\n    /**\n     * Mouse Tracking, this can be one of the following:\n     * - none: This is the default value and can be reset with DECRST\n     * - x10: Send Mouse X & Y on button press `CSI ? 9 h`\n     * - vt200: Send Mouse X & Y on button press and release `CSI ? 1 0 0 0 h`\n     * - drag: Use Cell Motion Mouse Tracking `CSI ? 1 0 0 2 h`\n     * - any: Use All Motion Mouse Tracking `CSI ? 1 0 0 3 h`\n     */\n    readonly mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any';\n    /**\n     * Origin Mode (DECOM): `CSI ? 6 h`\n     */\n    readonly originMode: boolean;\n    /**\n     * Reverse-wraparound Mode: `CSI ? 4 5 h`\n     */\n    readonly reverseWraparoundMode: boolean;\n    /**\n     * Send FocusIn/FocusOut events: `CSI ? 1 0 0 4 h`\n     */\n    readonly sendFocusMode: boolean;\n    /**\n     * Show Cursor (DECTCEM): `CSI ? 2 5 h`\n     */\n    readonly showCursor: boolean;\n    /**\n     * Synchronized Output Mode: `CSI ? 2 0 2 6 h`\n     *\n     * When enabled, output is buffered and only rendered when the mode is\n     * disabled, allowing for atomic screen updates without tearing.\n     */\n    readonly synchronizedOutputMode: boolean;\n    /**\n     * Win32 Input Mode: `CSI ? 9 0 0 1 h`\n     *\n     * When enabled, keyboard input is sent as Win32 INPUT_RECORD format:\n     * `CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _`\n     */\n    readonly win32InputMode: boolean;\n    /**\n     * Auto-Wrap Mode (DECAWM): `CSI ? 7 h`\n     */\n    readonly wraparoundMode: boolean;\n  }\n\n  /**\n   * An object containing a width and height in pixels.\n   */\n  export interface IDimensions {\n    width: number;\n    height: number;\n  }\n\n  /**\n   * An object containing a top and left offset.\n   */\n  export interface IOffset {\n    top: number;\n    left: number;\n  }\n\n  /**\n   * The dimensions of the terminal.\n   */\n  export interface IRenderDimensions {\n    /**\n     * Dimensions measured in CSS pixels (ie. device pixels / device pixel\n     * ratio).\n     */\n    css: {\n      /**\n       * The dimensions of the canvas.\n       */\n      canvas: IDimensions;\n      /**\n       * The dimensions of a single cell.\n       */\n      cell: IDimensions;\n    };\n    /**\n     * Dimensions measured in actual pixels as rendered to the device.\n     */\n    device: {\n      /**\n       * The dimensions of the canvas.\n       */\n      canvas: IDimensions;\n      /**\n       * The dimensions of a single cell.\n       */\n      cell: IDimensions;\n      /**\n       * The dimensions of a single character within a cell, including its\n       * offset within the cell.\n       */\n      char: IDimensions & IOffset;\n    };\n  }\n\n  /**\n   * An object containing a width and height in pixels.\n   */\n  export interface IDimensions {\n    width: number;\n    height: number;\n  }\n\n  /**\n   * An object containing a top and left offset.\n   */\n  export interface IOffset {\n    top: number;\n    left: number;\n  }\n\n  /**\n   * The dimensions of the terminal, this is constructed and available after\n   * {@link Terminal.open} is called.\n   */\n  export interface IRenderDimensions {\n    /**\n     * Dimensions measured in CSS pixels (ie. device pixels / device pixel\n     * ratio).\n     */\n    css: {\n      /**\n       * The dimensions of the canvas which is the full terminal size.\n       */\n      canvas: IDimensions;\n      /**\n       * The dimensions of a single cell.\n       */\n      cell: IDimensions;\n    };\n    /**\n     * Dimensions measured in actual pixels as rendered to the device.\n     */\n    device: {\n      /**\n       * The dimensions of the canvas which is the full terminal size.\n       */\n      canvas: IDimensions;\n      /**\n       * The dimensions of a single cell.\n       */\n      cell: IDimensions;\n      /**\n       * The dimensions of a single character within a cell, including its\n       * offset within the cell.\n       */\n      char: IDimensions & IOffset;\n    };\n  }\n}\n"
  },
  {
    "path": "webpack.config.headless.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst path = require('path');\n\n/**\n * This webpack config does a production build for xterm.js headless. It works by taking the output\n * from tsc (via `npm run watch` or `npm run prebuild`) which are put into `out/` and webpacks them\n * into a production mode umd library module in `lib-headless/`. The aliases are used fix up the\n * absolute paths output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`.\n *\n * @type {import('webpack').Configuration}\n */\nconst config = {\n  entry: './out/headless/public/Terminal.js',\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('./out/common'),\n      headless: path.resolve('./out/headless'),\n      vs: path.resolve('./out/vs')\n    }\n  },\n  output: {\n    filename: 'xterm-headless.js',\n    path: path.resolve('./headless/lib-headless'),\n    library: {\n      type: 'commonjs'\n    },\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production',\n};\nmodule.exports = config;\n"
  },
  {
    "path": "webpack.config.js",
    "content": "/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// @ts-check\n\nconst path = require('path');\n\n/**\n * This webpack config does a production build for xterm.js. It works by taking the output from tsc\n * (via `npm run watch` or `npm run prebuild`) which are put into `out/` and webpacks them into a\n * production mode umd library module in `lib/`. The aliases are used fix up the absolute paths\n * output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`.\n *\n * @type {import('webpack').Configuration}\n */\nconst config = {\n  entry: './out/browser/public/Terminal.js',\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\"source-map-loader\"],\n        enforce: \"pre\",\n        exclude: /node_modules/\n      }\n    ]\n  },\n  resolve: {\n    modules: ['./node_modules'],\n    extensions: [ '.js' ],\n    alias: {\n      common: path.resolve('./out/common'),\n      browser: path.resolve('./out/browser'),\n      vs: path.resolve('./out/vs'),\n    }\n  },\n  output: {\n    filename: 'xterm.js',\n    path: path.resolve('./lib'),\n    libraryTarget: 'umd',\n    // Force usage of globalThis instead of global / self. (This is cross-env compatible)\n    globalObject: 'globalThis',\n  },\n  mode: 'production',\n};\nmodule.exports = config;\n"
  }
]