diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 891c6ecb..c65a2b3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: # we specify bash to get pipefail; it guards against the `curl` command # failing. otherwise `sh` won't catch that `curl` returned non-0 shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh" - name: Cache dist uses: actions/upload-artifact@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index c5172db5..a5bd154a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,23 @@ Changelog * No changes. +Version 0.1.46 -- 2025-Dec-15 +-------------------------------------------------------------------------------- + +* Avoid spaces disappearing in Client when editing lists. +* Improve speed of convert +* Improve Client error reporting. + +Version 0.1.45 -- 2025-Dec-11 +-------------------------------------------------------------------------------- + +* Fix loss of editing in the Client when in document-only mode. +* Fix data corruption in the Client when in document-only mode and edits are + made in both the IDE and Client. +* Fix loss of autosave when making GUI-based edits in the Client. +* Correctly retain Client cursor position during IDE edits. +* Correctly translate table cells containing blocks from HTML to Markdown. + Version 0.1.44 -- 2025-Dec-09 -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index d9d35962..2db004a5 100644 --- a/README.md +++ b/README.md @@ -159,14 +159,14 @@ graph TD; A --> B; -The [Mermaid live editor](https://mermaid.live/) provide an focused environment for creating Mermaid chart. +The [Mermaid live editor](https://mermaid.live/) provide an focused environment +for creating Mermaid chart. ### Graphviz The CodeChat Editor supports diagrams created by [Graphviz](https://graphviz.org/). For example, - @@ -195,8 +195,8 @@ digraph { A -> B }
- -Several on-line tools, such as [Edotor](https://edotor.net/), provide a focused editing experience. +Several on-line tools, such as [Edotor](https://edotor.net/), provide a focused +editing experience. ### PlantUML diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 4645dca5..acfe4994 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -23,12 +23,6 @@ name = "builder" version = "0.1.0" edition = "2024" -# To make VSCode happy, create a unused `int_test` feature, then enable this -# feature in the VSCode Rust language server for a better experience. See -# `server/Cargo.toml` for its actual use. -[features] -int_tests = [] - [dependencies] clap = { version = "4.5.19", features = ["derive"] } cmd_lib = "2.0.0" diff --git a/builder/src/main.rs b/builder/src/main.rs index 8c672258..7849dc8d 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -121,6 +121,7 @@ struct TypeScriptBuildOptions { static VSCODE_PATH: &str = "../extensions/VSCode"; static CLIENT_PATH: &str = "../client"; static BUILDER_PATH: &str = "../builder"; +static TEST_UTILS_PATH: &str = "../test_utils"; static NAPI_TARGET: &str = "NAPI_TARGET"; // Code @@ -311,15 +312,6 @@ fn patch_file(patch: &str, before_patch: &str, file_path: &str) -> io::Result<() } /// After updating files in the client's Node files, perform some fix-ups. fn patch_client_libs() -> io::Result<()> { - // Apply a the fixes described in [issue - // 27](https://github.com/bjones1/CodeChat_Editor/issues/27). - patch_file( - " - selectionNotFocus = this.view.state.facet(editable) ? focused : hasSelection(this.dom, this.view.observer.selectionRange)", - " let selectionNotFocus = !focused && !(this.view.state.facet(editable) || this.dom.tabIndex > -1) && - hasSelection(this.dom, this.view.observer.selectionRange) && !(activeElt && this.dom.contains(activeElt));", - &format!("{CLIENT_PATH}/node_modules/@codemirror/view/dist/index.js") - )?; // In [older // releases](https://www.tiny.cloud/docs/tinymce/5/6.0-upcoming-changes/#options), // TinyMCE allowed users to change `whitespace_elements`; the whitespace @@ -369,6 +361,8 @@ fn run_install(dev: bool) -> io::Result<()> { cargo fetch --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo fetch"; cargo fetch --manifest-path=$VSCODE_PATH/Cargo.toml; + info "test_utils: cargo fetch" + cargo fetch --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo fetch"; cargo fetch; )?; @@ -420,6 +414,8 @@ fn run_update() -> io::Result<()> { cargo update --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCoe extension: cargo update"; cargo update --manifest-path=$VSCODE_PATH/Cargo.toml; + info "test_utils: cargo update" + cargo update --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo update"; cargo update; )?; @@ -431,6 +427,8 @@ fn run_update() -> io::Result<()> { cargo outdated --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo outdated"; cargo outdated --manifest-path=$VSCODE_PATH/Cargo.toml; + info "test_utils: cargo outdated" + cargo outdated --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo outdated"; cargo outdated; )?; @@ -440,10 +438,10 @@ fn run_update() -> io::Result<()> { fn run_format_and_lint(check_only: bool) -> io::Result<()> { // The `-D warnings` flag causes clippy to return a non-zero exit status if // it issues warnings. - let (clippy_check_only, check, prettier_check) = if check_only { - ("-Dwarnings", "--check", "--check") + let (clippy_check_only, check, eslint_check) = if check_only { + ("-Dwarnings", "--check", "") } else { - ("", "", "--write") + ("", "", "--fix") }; run_cmd!( info "cargo clippy and fmt"; @@ -455,6 +453,9 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { info "VSCode extension: cargo clippy and fmt"; cargo clippy --all-targets --all-features --tests --manifest-path=$VSCODE_PATH/Cargo.toml -- $clippy_check_only; cargo fmt --all $check --manifest-path=$VSCODE_PATH/Cargo.toml; + info "test_utils: cargo clippy and fmt" + cargo clippy --all-targets --all-features --tests --manifest-path=$TEST_UTILS_PATH/Cargo.toml -- $clippy_check_only; + cargo fmt --all $check --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo sort"; cargo sort $check; cd $BUILDER_PATH; @@ -463,19 +464,17 @@ fn run_format_and_lint(check_only: bool) -> io::Result<()> { cd $VSCODE_PATH; info "VSCode extension: cargo sort"; cargo sort $check; + info "test_utils: cargo sort" + cd $TEST_UTILS_PATH; + cargo sort $check; + )?; - run_script( - "npx", - &["prettier", "src", prettier_check], - CLIENT_PATH, - true, - )?; - run_script( - "npx", - &["prettier", "src", prettier_check], - VSCODE_PATH, - true, - ) + let mut eslint_args = vec!["eslint", "src"]; + if !eslint_check.is_empty() { + eslint_args.push(eslint_check) + } + run_script("npx", &eslint_args, CLIENT_PATH, true)?; + run_script("npx", &eslint_args, VSCODE_PATH, true) } fn run_test() -> io::Result<()> { @@ -492,8 +491,10 @@ fn run_test() -> io::Result<()> { cargo test --manifest-path=$BUILDER_PATH/Cargo.toml; info "VSCode extension: cargo test"; cargo test --manifest-path=$VSCODE_PATH/Cargo.toml; + info "test_utils: cargo test" + cargo test --manifest-path=$TEST_UTILS_PATH/Cargo.toml; info "cargo test"; - cargo test --features int_tests; + cargo test; )?; Ok(()) } diff --git a/client/.eslintrc.yml b/client/.eslintrc.yml deleted file mode 100644 index 73de1d4a..00000000 --- a/client/.eslintrc.yml +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2025 Bryan A. Jones. -# -# This file is part of the CodeChat Editor. -# -# The CodeChat Editor is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# The CodeChat Editor is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# the CodeChat Editor. If not, see -# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). -# -# `.eslintrc.yml` -- Configure ESLint for this project -# ==================================================== - -env: - browser: true - es2020: true -extends: - - standard - # See the [ESLint config prettier - # docs](https://github.com/prettier/eslint-config-prettier#installation) and - # its parent link, [integrating Prettier with - # linters](https://prettier.io/docs/en/integrating-with-linters.html). - - prettier -parser: "@typescript-eslint/parser" -parserOptions: - ecmaVersion: latest -plugins: - - "@typescript-eslint" -rules: - camelcase: off - # TypeScript already enforces this; otherwise, eslint complains that - # `NodeJS` is undefined. See [this GitHub - # issue](https://github.com/Chatie/eslint-config/issues/45#issuecomment-1003990077). - no-undef: off diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 00000000..ccec075f --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,54 @@ +// Copyright (C) 2025 Bryan A. Jones. +// +// This file is part of the CodeChat Editor. +// +// The CodeChat Editor is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// The CodeChat Editor is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// the CodeChat Editor. If not, see +// [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). +// +// `.eslintrc.yml` -- Configure ESLint for this project +// ==================================================== +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import eslint from "@eslint/js"; +import { defineConfig } from "eslint/config"; +import tseslint from "typescript-eslint"; + +export default defineConfig( + eslint.configs.recommended, + tseslint.configs.recommended, + eslintPluginPrettierRecommended, + defineConfig([ + { + // This must be the only key in this dict to be treated as a global ignore. Only global ignores can ignore directories. See the [docs](https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores). + ignores: ["src/third-party/**"], + }, + { + name: "local", + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + }, + }, + ]), +); diff --git a/client/package.json5 b/client/package.json5 index e6e085b5..b526dbe8 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -43,7 +43,7 @@ url: 'https://github.com/bjones1/CodeChat_editor', }, type: 'module', - version: '0.1.44', + version: '0.1.46', dependencies: { '@codemirror/commands': '^6.10.0', '@codemirror/lang-cpp': '^6.0.3', @@ -61,34 +61,37 @@ '@codemirror/lang-xml': '^6.1.0', '@codemirror/lang-yaml': '^6.1.2', '@codemirror/state': '^6.5.2', - '@codemirror/view': '=6.38.8', - '@hpcc-js/wasm-graphviz': '^1.16.0', - '@mathjax/mathjax-newcm-font': '4.0.0', + '@codemirror/view': '^6.39.4', + '@hpcc-js/wasm-graphviz': '^1.17.0', + '@mathjax/mathjax-newcm-font': '4.1.0', codemirror: '^6.0.2', mathjax: '4.0.0', mermaid: '^11.12.2', - 'npm-check-updates': '^19.1.2', + 'npm-check-updates': '^19.2.0', 'pdfjs-dist': '^5.4.449', - tinymce: '^8.2.2', + tinymce: '^8.3.0', 'toastify-js': '^1.12.0', }, devDependencies: { + '@eslint/js': '^9.39.2', '@types/chai': '^5.2.3', + '@types/dom-navigation': '^1.0.6', '@types/js-beautify': '^1.14.3', '@types/mocha': '^10.0.10', - '@types/node': '^24.10.2', + '@types/node': '^24.10.4', '@types/toastify-js': '^1.12.4', - '@typescript-eslint/eslint-plugin': '^8.49.0', - '@typescript-eslint/parser': '^8.49.0', + '@typescript-eslint/eslint-plugin': '^8.50.0', + '@typescript-eslint/parser': '^8.50.0', chai: '^6.2.1', esbuild: '^0.27.1', - eslint: '^9.39.1', + eslint: '^9.39.2', 'eslint-config-prettier': '^10.1.8', 'eslint-plugin-import': '^2.32.0', 'eslint-plugin-prettier': '^5.5.4', mocha: '^11.7.5', prettier: '^3.7.4', typescript: '^5.9.3', + 'typescript-eslint': '^8.50.0', }, scripts: { test: 'echo "Error: no test specified" && exit 1', diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 143a9048..bd981845 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -57,14 +57,14 @@ importers: specifier: ^6.5.2 version: 6.5.2 '@codemirror/view': - specifier: =6.38.8 - version: 6.38.8 + specifier: ^6.39.4 + version: 6.39.4 '@hpcc-js/wasm-graphviz': - specifier: ^1.16.0 - version: 1.16.0 + specifier: ^1.17.0 + version: 1.17.0 '@mathjax/mathjax-newcm-font': - specifier: 4.0.0 - version: 4.0.0 + specifier: 4.1.0 + version: 4.1.0 codemirror: specifier: ^6.0.2 version: 6.0.2 @@ -75,21 +75,27 @@ importers: specifier: ^11.12.2 version: 11.12.2 npm-check-updates: - specifier: ^19.1.2 - version: 19.1.2 + specifier: ^19.2.0 + version: 19.2.0 pdfjs-dist: specifier: ^5.4.449 version: 5.4.449 tinymce: - specifier: ^8.2.2 - version: 8.2.2 + specifier: ^8.3.0 + version: 8.3.0 toastify-js: specifier: ^1.12.0 version: 1.12.0 devDependencies: + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 '@types/chai': specifier: ^5.2.3 version: 5.2.3 + '@types/dom-navigation': + specifier: ^1.0.6 + version: 1.0.6 '@types/js-beautify': specifier: ^1.14.3 version: 1.14.3 @@ -97,17 +103,17 @@ importers: specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: ^24.10.2 - version: 24.10.2 + specifier: ^24.10.4 + version: 24.10.4 '@types/toastify-js': specifier: ^1.12.4 version: 1.12.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.49.0 - version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.50.0 + version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.49.0 - version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.50.0 + version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) chai: specifier: ^6.2.1 version: 6.2.1 @@ -115,17 +121,17 @@ importers: specifier: ^0.27.1 version: 0.27.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.1) + version: 10.1.8(eslint@9.39.2) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) eslint-plugin-prettier: specifier: ^5.5.4 - version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.4) + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2))(eslint@9.39.2)(prettier@3.7.4) mocha: specifier: ^11.7.5 version: 11.7.5 @@ -135,6 +141,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.50.0 + version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) packages: @@ -219,8 +228,8 @@ packages: '@codemirror/state@6.5.2': resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/view@6.38.8': - resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} + '@codemirror/view@6.39.4': + resolution: {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==} '@esbuild/aix-ppc64@0.27.1': resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} @@ -404,8 +413,8 @@ packages: resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -416,8 +425,8 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@hpcc-js/wasm-graphviz@1.16.0': - resolution: {integrity: sha512-N7IcFIF6Va9Ko3DT5+N3crI5AHE71BH/H+frxfqgo7fn2LxzGaWDHPSRUsLfCCt3cpAFqsdkyoIc//FA+HxKzw==} + '@hpcc-js/wasm-graphviz@1.17.0': + resolution: {integrity: sha512-EeQUvc41JrFU7AWkh3//KdgRT9waJrBxFA7H9m3uI/zAHzbvTgbw05tik+fxuDPD0sXRxc8g2kXqC5jMobWUQA==} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -496,8 +505,8 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} - '@mathjax/mathjax-newcm-font@4.0.0': - resolution: {integrity: sha512-kpsJgIF4FpWiwIkFgOPmWwy5GXfL25spmJJNg27HQxPddmEL8Blx0jn2BuU/nlwjM/9SnYpEfDrWiAMgLPlB8Q==} + '@mathjax/mathjax-newcm-font@4.1.0': + resolution: {integrity: sha512-n10AwYubUa2hyOzxSRzkwRrgCVns083zkentryXICMPKaWT/watfvK2sUk5D9Bow9mpDfoqb5EWApuUvqnlzaw==} '@mermaid-js/parser@0.6.3': resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} @@ -676,6 +685,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/dom-navigation@1.0.6': + resolution: {integrity: sha512-4srBpebg8rFDm0LafYuWhZMgLoSr5J4gx4q1uaTqOXwVk00y+CkTdJ4SC57sR1cMhP0ZRjApMRdHVcFYOvPGTw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -694,8 +706,8 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@24.10.2': - resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + '@types/node@24.10.4': + resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} '@types/toastify-js@1.12.4': resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==} @@ -703,63 +715,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.49.0': - resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + '@typescript-eslint/eslint-plugin@8.50.0': + resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.49.0 + '@typescript-eslint/parser': ^8.50.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.49.0': - resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + '@typescript-eslint/parser@8.50.0': + resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.49.0': - resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + '@typescript-eslint/project-service@8.50.0': + resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.49.0': - resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} + '@typescript-eslint/scope-manager@8.50.0': + resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.49.0': - resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + '@typescript-eslint/tsconfig-utils@8.50.0': + resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.49.0': - resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + '@typescript-eslint/type-utils@8.50.0': + resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.49.0': - resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + '@typescript-eslint/types@8.50.0': + resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.49.0': - resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + '@typescript-eslint/typescript-estree@8.50.0': + resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.49.0': - resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} + '@typescript-eslint/utils@8.50.0': + resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.49.0': - resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + '@typescript-eslint/visitor-keys@8.50.0': + resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -1153,8 +1165,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -1260,8 +1272,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1675,8 +1687,8 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - npm-check-updates@19.1.2: - resolution: {integrity: sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==} + npm-check-updates@19.2.0: + resolution: {integrity: sha512-XSIuL0FNgzXPDZa4lje7+OwHjiyEt84qQm6QMsQRbixNY5EHEM9nhgOjxjlK9jIbN+ysvSqOV8DKNS0zydwbdg==} engines: {node: '>=20.0.0', npm: '>=8.12.1'} hasBin: true @@ -1969,8 +1981,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinymce@8.2.2: - resolution: {integrity: sha512-CFDSZwciMvFGW2czK/Xig1HcOGpXI0qcQMIqaIcG2F4RuuTdf+LQTreyEZunAJoFTQ9L0KAugOqL7OA5TJkoAA==} + tinymce@8.3.0: + resolution: {integrity: sha512-9IjrEo8HD5mg9QP6/rKcPSIcyRNVSf5eiYTqapb/q1zAIoISRJgI2DJUs4CJgZvio0hmEH394xSHUJuoGf4Msw==} toastify-js@1.12.0: resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} @@ -2008,6 +2020,13 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typescript-eslint@8.50.0: + resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -2139,14 +2158,14 @@ snapshots: dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@codemirror/commands@6.10.0': dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@codemirror/lang-cpp@6.0.3': @@ -2177,7 +2196,7 @@ snapshots: '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@lezer/html': 1.3.12 @@ -2193,7 +2212,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@lezer/javascript': 1.5.4 @@ -2208,7 +2227,7 @@ snapshots: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@lezer/markdown': 1.6.1 @@ -2247,7 +2266,7 @@ snapshots: '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@lezer/xml': 1.0.6 @@ -2264,7 +2283,7 @@ snapshots: '@codemirror/language@6.11.3': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.5 @@ -2273,20 +2292,20 @@ snapshots: '@codemirror/lint@6.9.2': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 crelt: 1.0.6 '@codemirror/search@6.5.11': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 crelt: 1.0.6 '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.38.8': + '@codemirror/view@6.39.4': dependencies: '@codemirror/state': 6.5.2 crelt: 1.0.6 @@ -2371,9 +2390,9 @@ snapshots: '@esbuild/win32-x64@0.27.1': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': dependencies: - eslint: 9.39.1 + eslint: 9.39.2 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2408,7 +2427,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.39.1': {} + '@eslint/js@9.39.2': {} '@eslint/object-schema@2.1.7': {} @@ -2417,7 +2436,7 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@hpcc-js/wasm-graphviz@1.16.0': {} + '@hpcc-js/wasm-graphviz@1.17.0': {} '@humanfs/core@0.19.1': {} @@ -2536,7 +2555,7 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@mathjax/mathjax-newcm-font@4.0.0': {} + '@mathjax/mathjax-newcm-font@4.1.0': {} '@mermaid-js/parser@0.6.3': dependencies: @@ -2717,6 +2736,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/dom-navigation@1.0.6': {} + '@types/estree@1.0.8': {} '@types/geojson@7946.0.16': {} @@ -2729,7 +2750,7 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@24.10.2': + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 @@ -2738,15 +2759,15 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 - eslint: 9.39.1 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 + eslint: 9.39.2 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -2754,56 +2775,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.1 + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.49.0': + '@typescript-eslint/scope-manager@8.50.0': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 - '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.1 + eslint: 9.39.2 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.49.0': {} + '@typescript-eslint/types@8.50.0': {} - '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 semver: 7.7.3 @@ -2813,20 +2834,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.50.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - eslint: 9.39.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.49.0': + '@typescript-eslint/visitor-keys@8.50.0': dependencies: - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/types': 8.50.0 eslint-visitor-keys: 4.2.1 acorn-jsx@5.3.2(acorn@8.15.0): @@ -2864,7 +2885,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 @@ -2875,7 +2896,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 @@ -2884,14 +2905,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: @@ -2899,7 +2920,7 @@ snapshots: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -2985,7 +3006,7 @@ snapshots: '@codemirror/lint': 6.9.2 '@codemirror/search': 6.5.11 '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.8 + '@codemirror/view': 6.39.4 color-convert@2.0.1: dependencies: @@ -3273,7 +3294,7 @@ snapshots: emoji-regex@9.2.2: {} - es-abstract@1.24.0: + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -3388,9 +3409,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.1): + eslint-config-prettier@10.1.8(eslint@9.39.2): dependencies: - eslint: 9.39.1 + eslint: 9.39.2 eslint-import-resolver-node@0.3.9: dependencies: @@ -3400,17 +3421,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - eslint: 9.39.1 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3419,9 +3440,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.1 + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -3433,20 +3454,20 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.4): + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2))(eslint@9.39.2)(prettier@3.7.4): dependencies: - eslint: 9.39.1 + eslint: 9.39.2 prettier: 3.7.4 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.39.1) + eslint-config-prettier: 10.1.8(eslint@9.39.2) eslint-scope@8.4.0: dependencies: @@ -3457,15 +3478,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.1: + eslint@9.39.2: dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.1 + '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 @@ -3853,7 +3874,7 @@ snapshots: mathjax@4.0.0: dependencies: - '@mathjax/mathjax-newcm-font': 4.0.0 + '@mathjax/mathjax-newcm-font': 4.1.0 mermaid@11.12.2: dependencies: @@ -3925,7 +3946,7 @@ snapshots: natural-compare@1.4.0: {} - npm-check-updates@19.1.2: {} + npm-check-updates@19.2.0: {} object-inspect@1.13.4: {} @@ -3944,14 +3965,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 object.values@1.2.1: dependencies: @@ -4049,7 +4070,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -4198,7 +4219,7 @@ snapshots: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -4252,7 +4273,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinymce@8.2.2: {} + tinymce@8.3.0: {} toastify-js@1.12.0: {} @@ -4306,6 +4327,17 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typescript-eslint@8.50.0(eslint@9.39.2)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} ufo@1.6.1: {} diff --git a/client/src/CodeChatEditor-test.mts b/client/src/CodeChatEditor-test.mts index f9999982..3f7346ca 100644 --- a/client/src/CodeChatEditor-test.mts +++ b/client/src/CodeChatEditor-test.mts @@ -27,7 +27,6 @@ import "mocha/mocha.js"; import "mocha/mocha.css"; import { EditorView } from "@codemirror/view"; import { ChangeSpec, EditorState, EditorSelection } from "@codemirror/state"; -import { exportedForTesting } from "./CodeChatEditor.mjs"; import { CodeMirror, CodeMirrorDocBlockTuple } from "./shared_types.mjs"; import { DocBlockPlugin, @@ -41,9 +40,6 @@ import { // // Nothing needed at present. // -// Provide convenient access to all functions tested here. -const {} = exportedForTesting; - // From [SO](https://stackoverflow.com/a/39914235). const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); @@ -58,7 +54,7 @@ window.CodeChatEditor_test = () => { ui: "tdd", // This is required to use Mocha's global teardown from the browser, // AFAIK. - /// @ts-ignore + /// @ts-expect-error("See above.") globalTeardown: [ () => { // On teardown, put the Mocha div at the beginning of the body. diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts index 9819e1e4..6d031c04 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -58,7 +58,6 @@ import { scroll_to_line as codemirror_scroll_to_line, set_CodeMirror_positions, } from "./CodeMirror-integration.mjs"; -import "./EditorComponents.mjs"; import "./graphviz-webcomponent-setup.mts"; // This must be imported *after* the previous setup import, so it's placed here, // instead of in the third-party category above. @@ -95,22 +94,6 @@ enum EditorMode { raw, } -// Since this is experimental, TypeScript doesn't define it. See the -// [docs](https://developer.mozilla.org/en-US/docs/Web/API/NavigateEvent). -interface NavigateEvent extends Event { - canIntercept: boolean; - destination: any; - downloadRequest: String | null; - formData: any; - hashChange: boolean; - info: any; - navigationType: String; - signal: AbortSignal; - userInitiated: boolean; - intercept: any; - scroll: any; -} - // Tell TypeScript about the global namespace this program defines. declare global { interface Window { @@ -129,7 +112,7 @@ declare global { show_toast: (text: string) => void; allow_navigation: boolean; }; - CodeChatEditor_test: any; + CodeChatEditor_test: unknown; } } @@ -223,8 +206,8 @@ const _open_lp = async ( // This works, but TypeScript marks it as an error. Ignore this error by // including the // [@ts-ignore directive](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html#ts-check). - /// @ts-ignore - const editorMode = EditorMode[urlParams.get("mode") ?? "edit"]; + /// @ts-expect-error("See above.") + const _editorMode = EditorMode[urlParams.get("mode") ?? "edit"]; // Get the [current_metadata](#current_metadata) from the // provided `code_chat_for_web` struct and store it as a global variable. @@ -264,10 +247,8 @@ const _open_lp = async ( // [handling editor events](https://www.tiny.cloud/docs/tinymce/6/events/#handling-editor-events), // this is how to create a TinyMCE event handler. setup: (editor: Editor) => { - // The - // [editor core events list](https://www.tiny.cloud/docs/tinymce/6/events/#editor-core-events) - // includes the`Dirty` event. - editor.on("Dirty", (_event: Event) => { + editor.on("dirty", () => { + tinymce.activeEditor!.setDirty(false); is_dirty = true; startAutosaveTimer(); }); @@ -275,17 +256,19 @@ const _open_lp = async ( }); tinymce.activeEditor!.focus(); } else { - // Save and restore cursor/scroll location after an update per the - // [docs](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.dom.bookmarkmanager). - // However, this doesn't seem to work for the cursor location. - // Perhaps when TinyMCE normalizes the document, this gets lost? - const bm = tinymce.activeEditor!.selection.getBookmark(); + // Save the cursor location before the update, then restore it + // afterwards, if TinyMCE has focus. + const sel = tinymce.activeEditor!.hasFocus() + ? saveSelection() + : undefined; doc_content = "Plain" in source ? source.Plain.doc : apply_diff_str(doc_content, source.Diff.doc); tinymce.activeEditor!.setContent(doc_content); - tinymce.activeEditor!.selection.moveToBookmark(bm); + if (sel !== undefined) { + restoreSelection(sel); + } } mathJaxTypeset(codechat_body); scroll_to_line(cursor_line, scroll_line); @@ -309,7 +292,7 @@ const _open_lp = async ( }; const save_lp = (is_dirty: boolean) => { - let update: UpdateMessageContents = { + const update: UpdateMessageContents = { // The Framework will fill in this value. file_path: "", }; @@ -321,7 +304,7 @@ const save_lp = (is_dirty: boolean) => { // Add the contents only if the document is dirty. if (is_dirty) { - /// @ts-expect-error + /// @ts-expect-error("Declare here; it will be completed later.") let code_mirror_diffable: CodeMirrorDiffable = {}; if (is_doc_only()) { // Untypeset all math before saving the document. @@ -330,14 +313,14 @@ const save_lp = (is_dirty: boolean) => { ) as HTMLDivElement; mathJaxUnTypeset(codechat_body); // To save a document only, simply get the HTML from the only Tiny - // MCE div. - const html = tinymce.activeEditor!.save(); + // MCE div. Update the `doc_contents` to stay in sync with the Server. + doc_content = tinymce.activeEditor!.save(); ( code_mirror_diffable as { Plain: CodeMirror; } ).Plain = { - doc: html, + doc: doc_content, doc_blocks: [], }; // Retypeset all math after saving the document. @@ -356,11 +339,104 @@ const save_lp = (is_dirty: boolean) => { return update; }; +export const saveSelection = () => { + // Changing the text inside TinyMCE causes it to loose a selection tied to a + // specific node. So, instead store the selection as an array of indices in + // the childNodes array of each element: for example, a given selection is + // element 10 of the root TinyMCE div's children (selecting an ol tag), + // element 5 of the ol's children (selecting the last li tag), element 0 of + // the li's children (a text node where the actual click landed; the offset + // in this node is placed in `selection_offset`.) + const sel = window.getSelection(); + const selection_path = []; + const selection_offset = sel?.anchorOffset; + if (sel?.anchorNode) { + // Find a path from the selection back to the containing div. + for ( + let current_node = sel.anchorNode, is_first = true; + // Continue until we find the div which contains the doc block + // contents: either it's not an element (such as a div), ... + current_node.nodeType !== Node.ELEMENT_NODE || + // or it's not the doc block contents div. + (!(current_node as Element).classList.contains( + "CodeChat-doc-contents", + ) && + // Sometimes, the parent of a custom node (`wc-mermaid`) skips the TinyMCE div and returns the overall div. I don't know why. + !(current_node as Element).classList.contains("CodeChat-doc")); + current_node = current_node.parentNode!, is_first = false + ) { + // Store the index of this node in its' parent list of child + // nodes/children. Use `childNodes` on the first iteration, since + // the selection is often in a text node, which isn't in the + // `parents` list. However, using `childNodes` all the time causes + // trouble when reversing the selection -- sometimes, the + // `childNodes` change based on whether text nodes (such as a + // newline) are included are not after tinyMCE parses the content. + const p = current_node.parentNode; + // In case we go off the rails, give up if there are no more parents. + if (p === null) { + return { + selection_path: [], + selection_offset: 0, + }; + } + selection_path.unshift( + Array.prototype.indexOf.call( + is_first ? p.childNodes : p.children, + current_node, + ), + ); + } + } + return { selection_path, selection_offset }; +}; + +// Restore the selection produced by `saveSelection` to the active TinyMCE +// instance. +export const restoreSelection = ({ + selection_path, + selection_offset, +}: { + selection_path: number[]; + selection_offset?: number; +}) => { + // Copy the selection over to TinyMCE by indexing the selection path to find + // the selected node. + if (selection_path.length && typeof selection_offset === "number") { + let selection_node = tinymce.activeEditor!.getContentAreaContainer(); + for ( + ; + selection_path.length && + // If something goes wrong, bail out instead of producing + // exceptions. + selection_node !== undefined; + selection_node = + // As before, use the more-consistent `children` except for the + // last element, where we might be selecting a `text` node. + ( + selection_path.length > 1 + ? selection_node.children + : selection_node.childNodes + )[selection_path.shift()!]! as HTMLElement + ); + // Exit on failure. + if (selection_node === undefined) { + return; + } + // Use that to set the selection. + tinymce.activeEditor!.selection.setCursorLocation( + selection_node, + // In case of edits, avoid an offset past the end of the node. + Math.min(selection_offset, selection_node.nodeValue?.length ?? 0), + ); + } +}; + // Per // [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples), // here's the least bad way to choose between the control key and the command // key. -const os_is_osx = +const _os_is_osx = navigator.platform.indexOf("Mac") === 0 || navigator.platform === "iPhone" ? true : false; @@ -378,7 +454,7 @@ const on_save = async (only_if_dirty: boolean = false) => { console_log( "CodeChat Editor Client: sent Update - saving document/updating cursor location.", ); - await new Promise(async (resolve) => { + await new Promise((resolve) => { webSocketComm.send_message({ Update: save_lp(is_dirty) }, () => resolve(0), ); @@ -491,8 +567,7 @@ const on_click = (event: MouseEvent) => { const save_then_navigate = (codeChatEditorUrl: URL) => { on_save(true).then((_value) => { // Avoid recursion! - /// @ts-ignore - navigation.removeEventListener("navigate", on_navigate); + window.navigation.removeEventListener("navigate", on_navigate); parent.window.CodeChatEditorFramework.webSocketComm.current_file( codeChatEditorUrl, ); @@ -509,6 +584,7 @@ const scroll_to_line = (cursor_line?: number, scroll_line?: number) => { } }; +/*eslint-disable-next-line @typescript-eslint/no-explicit-any */ export const console_log = (...args: any) => { if (DEBUG_ENABLED) { console.log(...args); @@ -534,12 +610,10 @@ export const on_error = (event: Event) => { // namespace. on_dom_content_loaded(async () => { // Intercept links in this document to save before following the link. - /// @ts-ignore - navigation.addEventListener("navigate", on_navigate); + window.navigation.addEventListener("navigate", on_navigate); const ccb = document.getElementById("CodeChat-sidebar") as | HTMLIFrameElement | undefined; - /// @ts-ignore ccb?.contentWindow?.navigation.addEventListener("navigate", on_navigate); document.addEventListener("click", on_click); // Provide basic error reporting for uncaught errors. diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 8a65f9d4..69b7634e 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -100,7 +100,7 @@ class WebSocketComm { // The `ReconnectingWebSocket` doesn't provide ALL the `WebSocket` // methods. Ignore this, since we can't use `ReconnectingWebSocket` as a // type. - /// @ts-ignore + /// @ts-expect-error("This is legacy, third-party code.") this.ws = new ReconnectingWebSocket!(ws_url); // Identify this client on connection. this.ws.onopen = () => { @@ -139,7 +139,7 @@ class WebSocketComm { // Process this message. switch (key) { - case "Update": + case "Update": { // Load this data in. const current_update = value as UpdateMessageContents; // The rest of this should run after all other messages have @@ -228,8 +228,9 @@ class WebSocketComm { this.send_result(id); }); break; + } - case "CurrentFile": + case "CurrentFile": { // Note that we can ignore `value[1]` (if the file is text // or binary); the server only sends text files here. const current_file = value[0] as string; @@ -263,8 +264,9 @@ class WebSocketComm { this.send_result(id); }); break; + } - case "Result": + case "Result": { // Cancel the timer for this message and remove it from // `pending_messages`. const pending_message = this.pending_messages[id]; @@ -284,8 +286,9 @@ class WebSocketComm { ); } break; + } - default: + default: { const msg = `Received unhandled message ${key}(${format_struct( value, )})`; @@ -296,11 +299,14 @@ class WebSocketComm { )})`, }); break; + } } }; } + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ send = (data: any) => this.ws.send(data); + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ close = (...args: any) => this.ws.close(...args); set_root_iframe_src = (url: string) => { @@ -408,7 +414,7 @@ const set_content = async ( cursor_line?: number, scroll_line?: number, ) => { - let client = get_client(); + const client = get_client(); if (client === undefined) { // See if this is the [simple viewer](#Client-simple-viewer). Otherwise, // it's just the bare document to replace. @@ -475,7 +481,7 @@ declare global { CodeChatEditorFramework: { webSocketComm: WebSocketComm; }; - CodeChatEditor_test: any; + CodeChatEditor_test: unknown; } } @@ -488,6 +494,7 @@ const show_toast = (text: string) => { }; // Format a complex data structure as a string when in debug mode. +/*eslint-disable-next-line @typescript-eslint/no-explicit-any */ export const format_struct = (complex_data_structure: any): string => DEBUG_ENABLED ? JSON.stringify(complex_data_structure).substring( @@ -496,6 +503,7 @@ export const format_struct = (complex_data_structure: any): string => ) : ""; +/*eslint-disable-next-line @typescript-eslint/no-explicit-any */ const report_error = (text: string, ...objs: any) => { console.error(text); if (objs !== undefined) { diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index 659c4f2c..18eda907 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -83,9 +83,15 @@ import { rust } from "@codemirror/lang-rust"; import { sql } from "@codemirror/lang-sql"; import { yaml } from "@codemirror/lang-yaml"; import { Editor, init, tinymce } from "./tinymce-config.mjs"; +import { EditorEvent, Events } from "tinymce"; // ### Local -import { set_is_dirty, startAutosaveTimer } from "./CodeChatEditor.mjs"; +import { + set_is_dirty, + startAutosaveTimer, + saveSelection, + restoreSelection, +} from "./CodeChatEditor.mjs"; import { CodeChatForWeb, CodeMirror, @@ -100,7 +106,6 @@ import { show_toast } from "./show_toast.mjs"; // Globals // ----------------------------------------------------------------------------- let current_view: EditorView; -let tinymce_singleton: Editor | undefined; // This indicates that a call to `on_dirty` is scheduled, but hasn't run yet. let on_dirty_scheduled = false; @@ -112,6 +117,8 @@ const decorationOptions = { declare global { interface Window { + // Tye `#types/MathJax` definitions are out of date. + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ MathJax: any; } } @@ -123,6 +130,12 @@ const docBlockFreezeAnnotation = Annotation.define(); // location updates. const noAutosaveAnnotation = Annotation.define(); +// Define a facet called when extensions produce an error. +const exceptionSink = EditorView.exceptionSink.of((exception) => { + show_toast(`Error: ${exception}`); + console.error(exception); +}); + // Doc blocks in CodeMirror // ----------------------------------------------------------------------------- // @@ -145,7 +158,7 @@ export const docBlockField = StateField.define({ // the initial value for the field, which is an empty set (no doc blocks). // Therefore, simply return an empty DecorationSet (oddly, the type of // [Decoration.none](https://codemirror.net/docs/ref/#view.Decoration^none)). - create(state: EditorState) { + create(_state: EditorState) { return Decoration.none; }, @@ -163,7 +176,7 @@ export const docBlockField = StateField.define({ } // See [is](https://codemirror.net/docs/ref/#state.StateEffect.is). Add // a doc block, as requested by this effect. - for (let effect of tr.effects) + for (const effect of tr.effects) if (effect.is(addDocBlock)) { // Check that we're not overwriting text. const newlines = current_view.state.doc @@ -190,6 +203,7 @@ export const docBlockField = StateField.define({ effect.value.indent, effect.value.delimiter, effect.value.content, + false, ), ...decorationOptions, }).range(effect.value.from, effect.value.to), @@ -259,7 +273,7 @@ export const docBlockField = StateField.define({ doc_blocks = doc_blocks.update({ // Remove the old doc block. We assume there's only one // block in the provided from/to range. - filter: (from, to, value) => from !== effect.value.from, + filter: (from, _to, _value) => from !== effect.value.from, filterFrom: effect.value.from, filterTo: effect.value.from, // This adds the replacement doc block with updated @@ -276,6 +290,8 @@ export const docBlockField = StateField.define({ prev.spec.widget.contents, effect.value.contents, ), + // If autosave is allowed (meaning no autosave is not true), then this data came from the user, not the IDE. + tr.annotation(noAutosaveAnnotation) !== true, ), ...decorationOptions, }).range(from, to), @@ -283,7 +299,7 @@ export const docBlockField = StateField.define({ }); } else if (effect.is(deleteDocBlock)) { doc_blocks = doc_blocks.update({ - filter: (from, to, value) => from !== effect.value.from, + filter: (from, _to, _value) => from !== effect.value.from, filterFrom: effect.value.from, filterTo: effect.value.from, }); @@ -304,8 +320,8 @@ export const docBlockField = StateField.define({ // This provides a straightforward path to transform the entire editor's // contents (including these doc blocks) to JSON, which can then be sent // back to the server for reassembly into a source file. - toJSON: (value: DecorationSet, state: EditorState) => { - let json = []; + toJSON: (value: DecorationSet, _state: EditorState) => { + const json = []; for (const iter = value.iter(); iter.value !== null; iter.next()) { const w = iter.value.spec.widget; json.push([iter.from, iter.to, w.indent, w.delimiter, w.contents]); @@ -315,7 +331,7 @@ export const docBlockField = StateField.define({ // For loading a file from the server back into the editor, use // [fromJSON](https://codemirror.net/docs/ref/#state.StateField^define^config.fromJSON). - fromJSON: (json: any, state: EditorState) => + fromJSON: (json: [CodeMirrorDocBlockTuple], _state: EditorState) => Decoration.set( json.map( ([ @@ -326,7 +342,12 @@ export const docBlockField = StateField.define({ contents, ]: CodeMirrorDocBlockTuple) => Decoration.replace({ - widget: new DocBlockWidget(indent, delimiter, contents), + widget: new DocBlockWidget( + indent, + delimiter, + contents, + false, + ), ...decorationOptions, }).range(from, to), ), @@ -366,6 +387,9 @@ type updateDocBlockType = { indent?: string; delimiter?: string; contents: string | StringDiff[]; + // True if this update comes from a user change, as opposed to an update + // received from the IDE. + is_user_change?: boolean; }; // Define an update. @@ -408,6 +432,7 @@ class DocBlockWidget extends WidgetType { readonly indent: string, readonly delimiter: string, readonly contents: string, + readonly is_user_change: boolean, ) { // TODO: I don't understand why I don't need to store the provided // parameters in the object: `this.indent = indent;`, etc. @@ -425,7 +450,7 @@ class DocBlockWidget extends WidgetType { // See [toDom](https://codemirror.net/docs/ref/#view.WidgetType.toDOM). toDOM() { // Wrap this in an enclosing div. - let wrap = document.createElement("div"); + const wrap = document.createElement("div"); wrap.className = "CodeChat-doc"; wrap.innerHTML = // This doc block's indent. TODO: allow paste, but must only allow @@ -450,6 +475,9 @@ class DocBlockWidget extends WidgetType { updateDOM(dom: HTMLElement, _view: EditorView): boolean { // If this change was produced by a user edit, then the DOM was already // updated. Stop here. + if (this.is_user_change) { + return true; + } (dom.childNodes[0] as HTMLDivElement).innerHTML = this.indent; // The contents div could be a TinyMCE instance, or just a plain div. @@ -459,10 +487,10 @@ class DocBlockWidget extends WidgetType { if (is_tinymce) { // Save the cursor location before the update, then restore it // afterwards, if TinyMCE has focus. - const sel = tinymce_singleton!.hasFocus() + const sel = tinymce.activeEditor!.hasFocus() ? saveSelection() : undefined; - tinymce_singleton!.setContent(this.contents); + tinymce.activeEditor!.setContent(this.contents); if (sel !== undefined) { restoreSelection(sel); } @@ -502,102 +530,6 @@ class DocBlockWidget extends WidgetType { } } -const saveSelection = () => { - // Changing the text inside TinyMCE causes it to loose a selection tied to a - // specific node. So, instead store the selection as an array of indices in - // the childNodes array of each element: for example, a given selection is - // element 10 of the root TinyMCE div's children (selecting an ol tag), - // element 5 of the ol's children (selecting the last li tag), element 0 of - // the li's children (a text node where the actual click landed; the offset - // in this node is placed in `selection_offset`.) - const sel = window.getSelection(); - let selection_path = []; - const selection_offset = sel?.anchorOffset; - if (sel?.anchorNode) { - // Find a path from the selection back to the containing div. - for ( - let current_node = sel.anchorNode, is_first = true; - // Continue until we find the div which contains the doc block - // contents: either it's not an element (such as a div), ... - current_node.nodeType !== Node.ELEMENT_NODE || - // or it's not the doc block contents div. - (!(current_node as Element).classList.contains( - "CodeChat-doc-contents", - ) && - // Sometimes, the parent of a custom node (`wc-mermaid`) skips the TinyMCE div and returns the overall div. I don't know why. - !(current_node as Element).classList.contains("CodeChat-doc")); - current_node = current_node.parentNode!, is_first = false - ) { - // Store the index of this node in its' parent list of child - // nodes/children. Use `childNodes` on the first iteration, since - // the selection is often in a text node, which isn't in the - // `parents` list. However, using `childNodes` all the time causes - // trouble when reversing the selection -- sometimes, the - // `childNodes` change based on whether text nodes (such as a - // newline) are included are not after tinyMCE parses the content. - let p = current_node.parentNode; - // In case we go off the rails, give up if there are no more parents. - if (p === null) { - return { - selection_path: [], - selection_offset: 0, - }; - } - selection_path.unshift( - Array.prototype.indexOf.call( - is_first ? p.childNodes : p.children, - current_node, - ), - ); - } - } - return { selection_path, selection_offset }; -}; - -// Restore the selection produced by `saveSelection` to the active TinyMCE -// instance. -const restoreSelection = ({ - selection_path, - selection_offset, -}: { - selection_path: number[]; - selection_offset?: number; -}) => { - // Copy the selection over to TinyMCE by indexing the selection path to find - // the selected node. - if (selection_path.length && typeof selection_offset === "number") { - let selection_node = tinymce_singleton!.getContentAreaContainer(); - for ( - ; - selection_path.length && - // If something goes wrong, bail out instead of producing - // exceptions. - selection_node !== undefined; - selection_node = - // As before, use the more-consistent `children` except for the - // last element, where we might be selecting a `text` node. - ( - selection_path.length > 1 - ? selection_node.children - : selection_node.childNodes - )[selection_path.shift()!]! as HTMLElement - ); - // Exit on failure. - if (selection_node === undefined) { - return; - } - // Use that to set the selection. - tinymce_singleton!.selection.setCursorLocation( - selection_node, - // In case of edits, avoid an offset past the end of the node. - Math.min( - selection_offset, - selection_node.nodeValue?.length ?? Number.MAX_VALUE, - ), - ); - } -}; - // Typeset the provided node; taken from the // [MathJax docs](https://docs.mathjax.org/en/latest/web/typeset.html#handling-asynchronous-typesetting). export const mathJaxTypeset = async ( @@ -606,6 +538,7 @@ export const mathJaxTypeset = async ( ) => { try { await window.MathJax.typesetPromise([node]); + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ } catch (err: any) { report_error(`Typeset failed: ${err.message}`); } @@ -615,6 +548,7 @@ export const mathJaxTypeset = async ( export const mathJaxUnTypeset = (node: HTMLElement) => { window.MathJax.startup.document .getMathItemsWithin(node) + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ .forEach((item: any) => { item.removeFromDocument(true); }); @@ -683,7 +617,7 @@ const on_dirty = ( let from; try { from = current_view.posAtDOM(target); - } catch (e) { + } catch (_e) { console.error("Unable to get position from DOM.", target); return; } @@ -696,7 +630,7 @@ const on_dirty = ( // the actual div. But I don't know how. mathJaxUnTypeset(contents_div); const contents = is_tinymce - ? tinymce_singleton!.save() + ? tinymce.activeEditor!.save() : contents_div.innerHTML; await mathJaxTypeset(contents_div); current_view.dispatch({ @@ -714,7 +648,7 @@ const on_dirty = ( export const DocBlockPlugin = ViewPlugin.fromClass( class { - constructor(view: EditorView) {} + constructor(_view: EditorView) {} update(update: ViewUpdate) { // If the editor doesn't have focus, ignore selection changes. This // avoid the case where cursor movement in the IDE produces @@ -729,14 +663,14 @@ export const DocBlockPlugin = ViewPlugin.fromClass( .between( main_selection.from, main_selection.to, - (from: number, to: number, value: Decoration) => { + (from: number, to: number, _value: Decoration) => { // Is this range contained within this doc block? If // the ranges also contains element outside it, then // don't capture focus. TODO: not certain on the // bounds -- should I use <= or <, etc.? if ( main_selection.from < from || - main_selection.to > main_selection.to + main_selection.to > to ) { return; } @@ -747,11 +681,14 @@ export const DocBlockPlugin = ViewPlugin.fromClass( const dom = dom_at_pos.node.childNodes[ dom_at_pos.offset ] as HTMLDivElement | null; - if (dom == null) { + if ( + dom == null || + dom.className !== "CodeChat-doc" + ) { return; } - // Give focus to the contents of the doc block. + // TODO: current, posToDom never gives us a doc block, even when the from/to is correct. So, we never get here. (dom.childNodes[1] as HTMLElement).focus(); }, ); @@ -764,7 +701,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // so it can be edited. A simpler alternative is to do this in the // update() method above, but this is VERY slow, since update is // called frequently. - focusin: (event: FocusEvent, view: EditorView) => { + focusin: (event: FocusEvent, _view: EditorView) => { const target_or_false = element_is_in_doc_block(event.target); if (!target_or_false) { return false; @@ -842,10 +779,8 @@ export const DocBlockPlugin = ViewPlugin.fromClass( const old_contents_div = document.createElement("div")!; old_contents_div.className = "CodeChat-doc-contents"; old_contents_div.contentEditable = "true"; - old_contents_div.replaceChildren( - ...tinymce_singleton!.getContentAreaContainer() - .childNodes, - ); + old_contents_div.innerHTML = + tinymce.activeEditor!.getContent(); tinymce_div.parentNode!.insertBefore( old_contents_div, null, @@ -859,7 +794,9 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // Setting the content makes TinyMCE consider it dirty // -- ignore this "dirty" event. - tinymce_singleton!.setContent(contents_div.innerHTML); + tinymce.activeEditor!.setContent( + contents_div.innerHTML, + ); contents_div.remove(); // The new div is now a TinyMCE editor. Retypeset this. await mathJaxTypeset(tinymce_div); @@ -867,7 +804,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // This process causes TinyMCE to lose focus. Restore // that. However, this causes TinyMCE to lose the // selection, which the next bit of code then restores. - tinymce_singleton!.focus(false); + tinymce.activeEditor!.focus(false); // Copy the selection over to TinyMCE by indexing the // selection path to find the selected node. @@ -919,8 +856,8 @@ const autosaveExtension = EditorView.updateListener.of( // detected for efficiency. if (!v.docChanged && v.transactions?.length) { // Check each effect of each transaction. - outer: for (let tr of v.transactions) { - for (let effect of tr.effects) { + outer: for (const tr of v.transactions) { + for (const effect of tr.effects) { // Look for a change to a doc block. if (effect.is(addDocBlock) || effect.is(updateDocBlock)) { isChanged = true; @@ -1057,6 +994,7 @@ export const CodeMirror_load = async ( parser, basicSetup, EditorView.lineWrapping, + exceptionSink, autosaveExtension, // Make tab an indent per the // [docs](https://codemirror.net/examples/tab/). TODO: @@ -1087,24 +1025,28 @@ export const CodeMirror_load = async ( state, scrollTo: scrollSnapshot, }); - tinymce_singleton = ( - await init({ - selector: "#TinyMCE-inst", - setup: (editor: Editor) => { - // See the - // [docs](https://www.tiny.cloud/docs/tinymce/latest/events/#editor-core-events). - editor.on("input", (event: any) => { + + await init({ + selector: "#TinyMCE-inst", + setup: (editor: Editor) => { + // See the + // [docs](https://www.tiny.cloud/docs/tinymce/latest/events/#editor-core-events). + // This is triggered on edits (just as the `input` event), but also when applying formatting changes, inserting images, etc. that the above callback misses. + editor.on( + "Dirty", + (event: EditorEvent) => { // Get the div TinyMCE stores edits in. TODO: find // documentation for `event.target.bodyElement`. - const target_or_false = event.target as HTMLElement; + tinymce.activeEditor!.setDirty(false); + const target_or_false = event.target?.bodyElement; if (target_or_false == null) { - return false; + return; } setTimeout(() => on_dirty(target_or_false)); - }); - }, - }) - )[0]; + }, + ); + }, + }); } else { // This contains a diff, instead of plain text. Apply the text diff. // @@ -1122,7 +1064,7 @@ export const CodeMirror_load = async ( // same transaction causes the text edits to modify from/to values in // the doc block effects, even when changes to the doc block state is // frozen. - const stateEffects: StateEffect[] = []; + const stateEffects: StateEffect[] = []; for (const transaction of codechat_for_web.source.Diff.doc_blocks) { if ("Add" in transaction) { const add = transaction.Add; @@ -1214,7 +1156,8 @@ export const CodeMirror_save = (): CodeMirrorDiffable => { const code_mirror: CodeMirror = current_view.state.toJSON( CodeMirror_JSON_fields, ); - delete (code_mirror as any).selection; + /// @ts-expect-error("This does exist.") + delete code_mirror.selection; return { Plain: code_mirror }; }; diff --git a/client/src/EditorComponents.mts b/client/src/EditorComponents.mts deleted file mode 100644 index 4491ec2d..00000000 --- a/client/src/EditorComponents.mts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2025 Bryan A. Jones. -// -// This file is part of the CodeChat Editor. The CodeChat Editor is free -// software: you can redistribute it and/or modify it under the terms of the GNU -// General Public License as published by the Free Software Foundation, either -// version 3 of the License, or (at your option) any later version. -// -// The CodeChat Editor is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -// details. -// -// You should have received a copy of the GNU General Public License along with -// the CodeChat Editor. If not, see -// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). -// -// `EditorComponents.mts` -- Custom HTML tags which provide authoring support -// for the CodeChat Editor -// ========================================================================== -// -// Create a combined editor/renderer component. It's not currently used, since -// TinyMCE doesn't allow the editor to be focused. -class GraphVizElement extends HTMLElement { - constructor() { - super(); - // Create the shadow DOM. - const shadowRoot = this.attachShadow({ mode: "open" }); - const editor = document.createElement("graphviz-script-editor"); - const graph = document.createElement("graphviz-graph"); - - // TODO: Copy other attributes (scale, tabs, etc.) which the editor and - // graph renderer support. - - // Propagate the initial value on this tag to the tags in the shadow - // DOM. - const dot = this.getAttribute("graph") ?? ""; - graph.setAttribute("graph", dot); - editor.setAttribute("value", dot); - - // Send edits to both this tag and the graphviz rendering tag. - editor.addEventListener("input", (event) => { - // Ignore InputEvents -- we want the custom event sent by this - // component, which contains new text for the graph. - if (event instanceof CustomEvent) { - const dot = (event as any).detail; - graph.setAttribute("graph", dot); - // Update the root component as well, so that this value will be - // correct when the user saves. - this.setAttribute("graph", dot); - } - }); - - // Populate the shadow DOM now that everything is ready. - shadowRoot.append(editor, graph); - } -} -customElements.define("graphviz-combined", GraphVizElement); diff --git a/client/src/HashReader.mts b/client/src/HashReader.mts index 9671e47b..b62ecf5a 100644 --- a/client/src/HashReader.mts +++ b/client/src/HashReader.mts @@ -67,7 +67,7 @@ const metafile: Metafile = JSON.parse(data); // Walk the file, looking for the names of specific entry points. Transform // those into paths used to import these files. -let outputContents: Record = {}; +const outputContents: Record = {}; let num_found = 0; for (const output in metafile.outputs) { const outputInfo = metafile.outputs[output]; diff --git a/client/src/assert.mts b/client/src/assert.mts index 93d3ddc3..f0d23a71 100644 --- a/client/src/assert.mts +++ b/client/src/assert.mts @@ -8,7 +8,7 @@ // [this](https://github.com/micromark/micromark/issues/87#issuecomment-908924233)). // Taken from the TypeScript // [docs](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#assertion-functions). -export function assert(condition: any, msg?: string): asserts condition { +export function assert(condition: boolean, msg?: string): asserts condition { if (!condition) { throw new Error(msg); } diff --git a/client/src/graphviz-webcomponent-setup.mts b/client/src/graphviz-webcomponent-setup.mts index 92a0ec0f..32478e5a 100644 --- a/client/src/graphviz-webcomponent-setup.mts +++ b/client/src/graphviz-webcomponent-setup.mts @@ -24,6 +24,7 @@ // Note that this must be in a separate module which is imported before the // graphviz webcomponent; see the [ESBuild // docs](https://esbuild.github.io/content-types/#real-esm-imports). +/*eslint-disable-next-line @typescript-eslint/no-explicit-any */ (window as any).graphvizWebComponent = { rendererUrl: "/static/bundled/renderer.js", delayWorkerLoading: true, diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts index 0017324c..77578f72 100644 --- a/client/src/tinymce-config.mts +++ b/client/src/tinymce-config.mts @@ -26,7 +26,7 @@ import { TinyMCE, } from "tinymce"; // TODO: The type of tinymce is broken; I don't know why. Here's a workaround. -export const tinymce = tinymce_ as any as TinyMCE; +export const tinymce = tinymce_ as unknown as TinyMCE; export { Editor }; // Default icons are required for TinyMCE 5.3 or above. @@ -77,7 +77,7 @@ export const init = async ( options: RawEditorOptions, ) => { // Merge the provided options with these default options. - let combinedOptions = Object.assign({}, options, { + const combinedOptions = Object.assign({}, options, { // See the list of // [plugins](https://www.tiny.cloud/docs/tinymce/6/plugins/). These must // be accompanied by the corresponding import above. @@ -156,7 +156,7 @@ export const init = async ( editor.ui.registry.addToggleButton("codeformat", { text: "<>", tooltip: "Format as code", - onAction: (_) => + onAction: () => editor.execCommand("mceToggleFormat", false, "code"), onSetup: (api) => { const changed = editor.formatter.formatChanged( diff --git a/client/tsconfig.json b/client/tsconfig.json index 5603c888..eb70fe9b 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -34,5 +34,5 @@ "rootDir": "src", "strict": true }, - "exclude": ["node_modules", "static", "HashReader.js"] + "exclude": ["node_modules", "static", "HashReader.js", "eslint.config.js"] } diff --git a/dist-workspace.toml b/dist-workspace.toml index 9a5d4fca..3a9d2c60 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -25,7 +25,7 @@ members = ["cargo:server/"] # Config for 'dist' [dist] # The preferred dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.30.2" +cargo-dist-version = "0.30.3" # CI backends to support ci = "github" # The installers to generate for each app diff --git a/docs/demo.md b/docs/demo.md new file mode 100644 index 00000000..019f6814 --- /dev/null +++ b/docs/demo.md @@ -0,0 +1,27 @@ +`demo.md` - A script for a video demonstration of the CodeChat Editor +================================================================================ + +1. Introduction - start with `translation.rs`. + 1. Point out TOC. + 2. Show that comments are treated as Markdown and rendered. + 3. Make edits on both sides. + 4. Show sync through hyperlinks and changing files; show sync when selecting + lines. + 5. Other features: images, hyperlinks, diagrams. +2. Installation -- as a VSCode installation (preferred) or as a command-line + application from the + [releases](https://github.com/bjones1/CodeChat_Editor/releases) page. +3. Running -- start the standalone app; switch to focus on the VSCode app. +4. Use + 1. Open some source code -- show the list of supported file types in the + readme. + 2. Talk about comment formatting (one space after) + 3. Format a document: hyperlink, headings, bold, monospace, code blocks, etc. + 4. Insert an image (for example, draw.io) + 5. Create a Mermaid diagram and a Graphviz diagram. + 6. Write an equation. + 7. Add a TOC and a readme. + 8. Talk about style -- document function parameters, + +TODO: I'd like a good project that needs all these items. Something simple. +Perhaps a programming challenge? diff --git a/docs/test.py b/docs/test.py new file mode 100644 index 00000000..b2acb32a --- /dev/null +++ b/docs/test.py @@ -0,0 +1 @@ +# A  diff --git a/new-project-template/README.md b/examples/new-project-template/README.md similarity index 100% rename from new-project-template/README.md rename to examples/new-project-template/README.md diff --git a/new-project-template/toc.md b/examples/new-project-template/toc.md similarity index 100% rename from new-project-template/toc.md rename to examples/new-project-template/toc.md diff --git a/examples/no-spoon/example_grid.png b/examples/no-spoon/example_grid.png new file mode 100644 index 00000000..23eef0d7 Binary files /dev/null and b/examples/no-spoon/example_grid.png differ diff --git a/examples/no-spoon/no_spoon.py b/examples/no-spoon/no_spoon.py new file mode 100644 index 00000000..52b0ac94 --- /dev/null +++ b/examples/no-spoon/no_spoon.py @@ -0,0 +1,97 @@ +# `no_spoon.py` - a CodinGame challenge +# ============================================================================== +# +# This file demonstrates the use of literate programming in solving the +# ["There is no Spoon"](https://www.codingame.com/ide/puzzle/there-is-no-spoon-episode-1) +# programming challenge. +# +# Input +# ------------------------------------------------------------------------------ +# +# First, read the input provided by the challenge: +# +# The number of cells on the X axis. +width = int(input()) +# The number of cells on the Y axis. +height = int(input()) +# Read the grid of cells: +# +# * Each line is `width` characters. +# * The characters are either `0` (occupied) or `.` (empty). +# +# From the website, here's an example grid: +# +# +# +# The text which creates this grid is: +# +# ``` +# 00 +# 0. +# ``` +# +# Store this an an array of lines; each lines contains these characters. +# Therefore, `line[y][x]` gives the cell at the coordinate $(𝑥,𝑦)$ (note the +# reversed order). +line = [] +for y in range(height): + line.append(input()) + +# Processing and output +# ------------------------------------------------------------------------------ +# +# From the rules: +# +# > To do this, you must find each (x1,y1) coordinates containing a node, and +# > display the (x2,y2) coordinates of the next node to the right, and the +# > (x3,y3) coordinates of the next node to the bottom within the grid. +# > +# > If a neighbor does not exist, you must output the coordinates -1 -1 instead +# > of (x2,y2) and/or (x3,y3). +# +# ### Approach +# +# Terminology: +# +# * A cell is one point in the grid. It may be empty or occupied. +# * A node is an occupied cell. +# +# Variable naming: based on the rules, define: +# +# * (`x1`, `y1`) is the coordinate of an (occupied) node. +# * (`x2`, `y2`) is the coordinate of the next node to the right. +# * (`x3`, `y3`) is the coordinates of the next node to the bottom. +# +# ### Implementation +# +# * Loop through each cell. If it's occupied (a node): +# +# * Look for a node to its right; if not found, return coordinates of +# $(−1,−1)$. +# * Look for a node to the bottom; if not found, return coordinates of +# $(−1,−1)$. +for x1 in range(width): + for y1 in range(height): + if line[y1][x1] == "0": + # This cell is occupied. First, search for the next node to the + # right (note the `+ 1`) with the same y coordinate but a greater x + # coordinate: + y2 = y1 + for x2 in range(x1 + 1, width): + if line[y2][x2] == "0": + break + else: + # This runs only if we didn't find an occupied node. + x2 = -1 + y2 = -1 + + # Do the same thing, but along the y axis. + x3 = x1 + for y3 in range(y1 + 1, height): + if line[y3][x3] == "0": + break + else: + x3 = -1 + y3 = -1 + + print(f"{x1} {y1} {x2} {y2} {x3} {y3}") \ No newline at end of file diff --git a/extensions/VSCode/.eslintrc.yml b/extensions/VSCode/.eslintrc.yml deleted file mode 100644 index 591075ff..00000000 --- a/extensions/VSCode/.eslintrc.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2025 Bryan A. Jones. -# -# This file is part of the CodeChat Editor. -# -# The CodeChat Editor is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# The CodeChat Editor is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# the CodeChat Editor. If not, see -# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). -# -# `.eslintrc.yml` - Configure ESLint for this project -# ============================================================================== -env: - commonjs: true - node: true -extends: - - standard - # See the - # [ESLint config prettier docs](https://github.com/prettier/eslint-config-prettier#installation) - # and its parent link, - # [integrating Prettier with linters](https://prettier.io/docs/en/integrating-with-linters.html). - - prettier -parser: "@typescript-eslint/parser" -parserOptions: - ecmaVersion: latest -plugins: - - "@typescript-eslint" -rules: - camelcase: off - # TypeScript already enforces this; otherwise, eslint complains that - # `NodeJS` is undefined. See - # [this GitHub issue](https://github.com/Chatie/eslint-config/issues/45#issuecomment-1003990077). - no-undef: off diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 81a7d93c..e67fccf7 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -72,7 +72,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", + "rand", "sha1", "smallvec", "tokio", @@ -345,6 +345,27 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "assert_fs" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9" +dependencies = [ + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + +[[package]] +name = "assertables" +version = "9.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59051ec02907378a67b0ba1b8631121f5388c8dbbb3cec8c749d8f93c2c3c211" + [[package]] name = "async-trait" version = "0.1.89" @@ -410,6 +431,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -522,7 +553,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.44" +version = "0.1.46" dependencies = [ "actix-files", "actix-http", @@ -540,13 +571,13 @@ dependencies = [ "dunce", "futures-util", "htmd", - "html5ever 0.36.1", + "html5ever", "imara-diff", "indoc", "lazy_static", "log", "log4rs", - "markup5ever_rcdom 0.36.0+unofficial", + "markup5ever_rcdom", "mime", "mime_guess", "minreq", @@ -555,12 +586,13 @@ dependencies = [ "path-slash", "pest", "pest_derive", - "phf 0.13.1", + "phf", "pulldown-cmark 0.13.0", - "rand 0.9.2", + "rand", "regex", "serde", "serde_json", + "test_utils", "thiserror 2.0.17", "tokio", "tokio-postgres", @@ -573,7 +605,7 @@ dependencies = [ [[package]] name = "codechat-editor-vscode-extension" -version = "0.1.44" +version = "0.1.46" dependencies = [ "codechat-editor-server", "log", @@ -659,6 +691,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.7" @@ -723,6 +780,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -745,6 +808,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "doc-comment" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" + [[package]] name = "dprint-core" version = "0.67.4" @@ -820,6 +889,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1008,6 +1087,30 @@ dependencies = [ "wasip2", ] +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.10.0", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.27" @@ -1062,22 +1165,11 @@ dependencies = [ [[package]] name = "htmd" version = "0.5.0" -source = "git+https://github.com/bjones1/htmd.git?branch=math-support#af6bc129874d33eb7f4d7fb6f0a39b04c668b2f5" -dependencies = [ - "html5ever 0.35.0", - "markup5ever_rcdom 0.35.0+unofficial", - "phf 0.13.1", -] - -[[package]] -name = "html5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#a8a3c34143ad5641baa784e43bc73668dbbebc2f" dependencies = [ - "log", - "markup5ever 0.35.0", - "match_token", + "html5ever", + "markup5ever_rcdom", + "phf", ] [[package]] @@ -1087,7 +1179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" dependencies = [ "log", - "markup5ever 0.36.1", + "markup5ever", ] [[package]] @@ -1251,6 +1343,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "imara-diff" version = "0.2.0" @@ -1421,6 +1529,12 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.8.1" @@ -1485,7 +1599,7 @@ dependencies = [ "log-mdc", "mock_instant", "parking_lot", - "rand 0.9.2", + "rand", "serde", "serde-value", "serde_json", @@ -1503,17 +1617,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "markup5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" -dependencies = [ - "log", - "tendril", - "web_atoms 0.1.3", -] - [[package]] name = "markup5ever" version = "0.36.1" @@ -1522,19 +1625,7 @@ checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" dependencies = [ "log", "tendril", - "web_atoms 0.2.0", -] - -[[package]] -name = "markup5ever_rcdom" -version = "0.35.0+unofficial" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" -dependencies = [ - "html5ever 0.35.0", - "markup5ever 0.35.0", - "tendril", - "xml5ever 0.35.0", + "web_atoms", ] [[package]] @@ -1543,21 +1634,10 @@ version = "0.36.0+unofficial" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e5fc8802e8797c0dfdd2ce5c21aa0aee21abbc7b3b18559100651b3352a7b63" dependencies = [ - "html5ever 0.36.1", - "markup5ever 0.36.1", + "html5ever", + "markup5ever", "tendril", - "xml5ever 0.36.1", -] - -[[package]] -name = "match_token" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", + "xml5ever", ] [[package]] @@ -1884,15 +1964,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared 0.11.3", -] - [[package]] name = "phf" version = "0.13.1" @@ -1900,38 +1971,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", - "phf_shared 0.13.1", + "phf_shared", "serde", ] -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", + "phf_generator", + "phf_shared", ] [[package]] @@ -1941,7 +1992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared 0.13.1", + "phf_shared", ] [[package]] @@ -1950,22 +2001,13 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", "syn 2.0.111", ] -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "phf_shared" version = "0.13.1" @@ -2006,7 +2048,7 @@ dependencies = [ "hmac", "md-5", "memchr", - "rand 0.9.2", + "rand", "sha2", "stringprep", ] @@ -2053,6 +2095,33 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -2106,15 +2175,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.2" @@ -2122,7 +2182,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -2132,15 +2192,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - [[package]] name = "rand_core" version = "0.9.3" @@ -2209,6 +2263,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2407,19 +2474,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -2428,31 +2482,19 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.13.1", + "phf_shared", "precomputed-hash", "serde", ] -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "string_cache_codegen" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -2513,6 +2555,19 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "tendril" version = "0.4.3" @@ -2533,6 +2588,21 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test_utils" +version = "0.1.0" +dependencies = [ + "assert_fs", + "assertables", + "log", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2682,11 +2752,11 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf 0.13.1", + "phf", "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.9.2", + "rand", "socket2 0.6.1", "tokio", "tokio-util", @@ -2987,28 +3057,16 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web_atoms" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" -dependencies = [ - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache 0.8.9", - "string_cache_codegen 0.5.4", -] - [[package]] name = "web_atoms" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acd0c322f146d0f8aad130ce6c187953889359584497dac6561204c8e17bb43d" dependencies = [ - "phf 0.13.1", - "phf_codegen 0.13.1", - "string_cache 0.9.0", - "string_cache_codegen 0.6.1", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", ] [[package]] @@ -3407,16 +3465,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "xml5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" -dependencies = [ - "log", - "markup5ever 0.35.0", -] - [[package]] name = "xml5ever" version = "0.36.1" @@ -3424,7 +3472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f57dd51b88a4b9f99f9b55b136abb86210629d61c48117ddb87f567e51e66be7" dependencies = [ "log", - "markup5ever 0.36.1", + "markup5ever", ] [[package]] diff --git a/extensions/VSCode/Cargo.toml b/extensions/VSCode/Cargo.toml index e3c449b8..44269383 100644 --- a/extensions/VSCode/Cargo.toml +++ b/extensions/VSCode/Cargo.toml @@ -32,7 +32,7 @@ license = "GPL-3.0-only" name = "codechat-editor-vscode-extension" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.44" +version = "0.1.46" [lib] crate-type = ["cdylib"] diff --git a/extensions/VSCode/eslint.config.js b/extensions/VSCode/eslint.config.js new file mode 100644 index 00000000..c0936860 --- /dev/null +++ b/extensions/VSCode/eslint.config.js @@ -0,0 +1,54 @@ +// Copyright (C) 2025 Bryan A. Jones. +// +// This file is part of the CodeChat Editor. +// +// The CodeChat Editor is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// The CodeChat Editor is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// the CodeChat Editor. If not, see +// [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). +// +// `.eslintrc.yml` -- Configure ESLint for this project +// ==================================================== +const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended"); +const eslint = require("@eslint/js"); +const { defineConfig } = require("eslint/config"); +const tseslint = require("typescript-eslint"); + +module.exports = defineConfig( + eslint.configs.recommended, + tseslint.configs.recommended, + eslintPluginPrettierRecommended, + defineConfig([ + { + // This must be the only key in this dict to be treated as a global ignore. Only global ignores can ignore directories. See the [docs](https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores). + ignores: ["src/third-party/**"], + }, + { + name: "local", + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + }, + }, + ]), +); diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 8728ac3b..6f536bee 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -19,6 +19,7 @@ "CodeChat Editor", "Visual Studio Code extension" ], + "type": "commonjs", "license": "GPL-3.0-only", "main": "out/extension.js", "napi": { @@ -40,7 +41,7 @@ "type": "git", "url": "https://github.com/bjones1/CodeChat_Editor" }, - "version": "0.1.44", + "version": "0.1.46", "activationEvents": [ "onCommand:extension.codeChatEditorActivate", "onCommand:extension.codeChatEditorDeactivate" @@ -81,24 +82,27 @@ "devDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", + "@eslint/js": "^9.39.2", "@napi-rs/cli": "^3.5.0", "@tybys/wasm-util": "^0.10.1", "@types/escape-html": "^1.0.4", - "@types/node": "^24.10.2", + "@types/node": "^24.10.4", "@types/vscode": "1.61.0", - "@typescript-eslint/eslint-plugin": "^8.49.0", - "@typescript-eslint/parser": "^8.49.0", + "@typescript-eslint/eslint-plugin": "^8.50.0", + "@typescript-eslint/parser": "^8.50.0", "@vscode/vsce": "^3.7.1", "chalk": "^5.6.2", "esbuild": "^0.27.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^5.5.4", "npm-run-all2": "^8.0.4", "ovsx": "^0.10.7", "prettier": "^3.7.4", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "typescript-eslint": "^8.50.0" }, "optionalDependencies": { "bufferutil": "^4.0.9" diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index 0cd404b8..0a501946 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -18,9 +18,12 @@ importers: '@emnapi/runtime': specifier: ^1.7.1 version: 1.7.1 + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 '@napi-rs/cli': specifier: ^3.5.0 - version: 3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.2) + version: 3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.4) '@tybys/wasm-util': specifier: ^0.10.1 version: 0.10.1 @@ -28,17 +31,17 @@ importers: specifier: ^1.0.4 version: 1.0.4 '@types/node': - specifier: ^24.10.2 - version: 24.10.2 + specifier: ^24.10.4 + version: 24.10.4 '@types/vscode': specifier: 1.61.0 version: 1.61.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.49.0 - version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.50.0 + version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.49.0 - version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.50.0 + version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) '@vscode/vsce': specifier: ^3.7.1 version: 3.7.1 @@ -49,17 +52,20 @@ importers: specifier: ^0.27.1 version: 0.27.1 eslint: - specifier: ^9.39.1 - version: 9.39.1 + specifier: ^9.39.2 + version: 9.39.2 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.1) + version: 10.1.8(eslint@9.39.2) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) eslint-plugin-node: specifier: ^11.1.0 - version: 11.1.0(eslint@9.39.1) + version: 11.1.0(eslint@9.39.2) + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2))(eslint@9.39.2)(prettier@3.7.4) npm-run-all2: specifier: ^8.0.4 version: 8.0.4 @@ -72,6 +78,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.50.0 + version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) optionalDependencies: bufferutil: specifier: ^4.0.9 @@ -328,8 +337,8 @@ packages: resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -360,8 +369,8 @@ packages: resolution: {integrity: sha512-SYLX05PwJVnW+WVegZt1T4Ip1qba1ik+pNJPDiqvk6zS5Y/i8PhRzLpGEtVd7sW0G8cMtkD8t4AZYhQwm8vnww==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/checkbox@5.0.2': - resolution: {integrity: sha512-iTPV4tMMct7iOpwer5qmTP7gjnk1VQJjsNfAaC2b8Q3qiuHM3K2yjjDr5u1MKfkrvp2JD4Flf8sIPpF21pmZmw==} + '@inquirer/checkbox@5.0.3': + resolution: {integrity: sha512-xtQP2eXMFlOcAhZ4ReKP2KZvDIBb1AnCfZ81wWXG3DXLVH0f0g4obE0XDPH+ukAEMRcZT0kdX2AS1jrWGXbpxw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -369,8 +378,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@6.0.2': - resolution: {integrity: sha512-A0/13Wyi+8iFeNDX6D4zZYKPoBLIEbE4K/219qHcnpXMer2weWvaTo63+2c7mQPPA206DEMSYVOPnEw3meOlCw==} + '@inquirer/confirm@6.0.3': + resolution: {integrity: sha512-lyEvibDFL+NA5R4xl8FUmNhmu81B+LDL9L/MpKkZlQDJZXzG8InxiqYxiAlQYa9cqLLhYqKLQwZqXmSTqCLjyw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -378,8 +387,8 @@ packages: '@types/node': optional: true - '@inquirer/core@11.0.2': - resolution: {integrity: sha512-lgMRx/n02ciiNELBvFLHtmcjbV5tf5D/I0UYfCg2YbTZWmBZ10/niLd3IjWBxz8LtM27xP+4oLEa06Slmb7p7A==} + '@inquirer/core@11.1.0': + resolution: {integrity: sha512-+jD/34T1pK8M5QmZD/ENhOfXdl9Zr+BrQAUc5h2anWgi7gggRq15ZbiBeLoObj0TLbdgW7TAIQRU2boMc9uOKQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -387,8 +396,8 @@ packages: '@types/node': optional: true - '@inquirer/editor@5.0.2': - resolution: {integrity: sha512-pXQ4Nf0qmFcJuYB6NlcIIxH6l6zKOwNg1Jh/ZRdKd2dTqBB4OXKUFbFwR2K4LVXVtq15ZFFatBVT+rerYR8hWQ==} + '@inquirer/editor@5.0.3': + resolution: {integrity: sha512-wYyQo96TsAqIciP/r5D3cFeV8h4WqKQ/YOvTg5yOfP2sqEbVVpbxPpfV3LM5D0EP4zUI3EZVHyIUIllnoIa8OQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -396,8 +405,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@5.0.2': - resolution: {integrity: sha512-siFG1swxfjFIOxIcehtZkh+KUNB/YCpyfHNEGu+nC/SBXIbgUWibvThLn/WesSxLRGOeSKdNKoTm+GQCKFm6Ww==} + '@inquirer/expand@5.0.3': + resolution: {integrity: sha512-2oINvuL27ujjxd95f6K2K909uZOU2x1WiAl7Wb1X/xOtL8CgQ1kSxzykIr7u4xTkXkXOAkCuF45T588/YKee7w==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -418,8 +427,8 @@ packages: resolution: {integrity: sha512-qXm6EVvQx/FmnSrCWCIGtMHwqeLgxABP8XgcaAoywsL0NFga9gD5kfG0gXiv80GjK9Hsoz4pgGwF/+CjygyV9A==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/input@5.0.2': - resolution: {integrity: sha512-hN2YRo1QiEc9lD3mK+CPnTS4TK2RhCMmMmP4nCWwTkmQL2vx9jPJWYk+rbUZpwR1D583ZJk1FI3i9JZXIpi/qg==} + '@inquirer/input@5.0.3': + resolution: {integrity: sha512-4R0TdWl53dtp79Vs6Df2OHAtA2FVNqya1hND1f5wjHWxZJxwDMSNB1X5ADZJSsQKYAJ5JHCTO+GpJZ42mK0Otw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -427,8 +436,8 @@ packages: '@types/node': optional: true - '@inquirer/number@4.0.2': - resolution: {integrity: sha512-4McnjTSYrlthNW1ojkkmP75WLRYhQs7GXm6pDDoIrHqJuV5uUYwfdbB0geHdaKMarAqJQgoOVjzIT0jdWCsKew==} + '@inquirer/number@4.0.3': + resolution: {integrity: sha512-TjQLe93GGo5snRlu83JxE38ZPqj5ZVggL+QqqAF2oBA5JOJoxx25GG3EGH/XN/Os5WOmKfO8iLVdCXQxXRZIMQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -436,8 +445,8 @@ packages: '@types/node': optional: true - '@inquirer/password@5.0.2': - resolution: {integrity: sha512-oSDziMKiw4G2e4zS+0JRfxuPFFGh6N/9yUaluMgEHp2/Yyj2JGwfDO7XbwtOrxVrz+XsP/iaGyWXdQb9d8A0+g==} + '@inquirer/password@5.0.3': + resolution: {integrity: sha512-rCozGbUMAHedTeYWEN8sgZH4lRCdgG/WinFkit6ZPsp8JaNg2T0g3QslPBS5XbpORyKP/I+xyBO81kFEvhBmjA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -445,8 +454,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@8.0.2': - resolution: {integrity: sha512-2zK5zY48fZcl6+gG4eqOC/UzZsJckHCRvjXoLuW4D8LKOCVGdcJiSKkLnumSZjR/6PXPINDGOrGHqNxb+sxJDg==} + '@inquirer/prompts@8.1.0': + resolution: {integrity: sha512-LsZMdKcmRNF5LyTRuZE5nWeOjganzmN3zwbtNfcs6GPh3I2TsTtF1UYZlbxVfhxd+EuUqLGs/Lm3Xt4v6Az1wA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -454,8 +463,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@5.0.2': - resolution: {integrity: sha512-AcNALEdQKUQDeJcpC1a3YC53m1MLv+sMUS+vRZ8Qigs1Yg3Dcdtmi82rscJplogKOY8CXkKW4wvVwHS2ZjCIBQ==} + '@inquirer/rawlist@5.1.0': + resolution: {integrity: sha512-yUCuVh0jW026Gr2tZlG3kHignxcrLKDR3KBp+eUgNz+BAdSeZk0e18yt2gyBr+giYhj/WSIHCmPDOgp1mT2niQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -463,8 +472,8 @@ packages: '@types/node': optional: true - '@inquirer/search@4.0.2': - resolution: {integrity: sha512-hg63w5toohdzE65S3LiGhdfIL0kT+yisbZARf7zw65PvyMUTutTN3eMAvD/B6y/25z88vTrB7kSB45Vz5CbrXg==} + '@inquirer/search@4.0.3': + resolution: {integrity: sha512-lzqVw0YwuKYetk5VwJ81Ba+dyVlhseHPx9YnRKQgwXdFS0kEavCz2gngnNhnMIxg8+j1N/rUl1t5s1npwa7bqg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -472,8 +481,8 @@ packages: '@types/node': optional: true - '@inquirer/select@5.0.2': - resolution: {integrity: sha512-JygTohvQxSNnvt7IKANVlg/eds+yN5sLRilYeGc4ri/9Aqi/2QPoXBMV5Cz/L1VtQv63SnTbPXJZeCK2pSwsOA==} + '@inquirer/select@5.0.3': + resolution: {integrity: sha512-M+ynbwS0ecQFDYMFrQrybA0qL8DV0snpc4kKevCCNaTpfghsRowRY7SlQBeIYNzHqXtiiz4RG9vTOeb/udew7w==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -989,6 +998,10 @@ packages: '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1071,8 +1084,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@24.10.2': - resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + '@types/node@24.10.4': + resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1083,63 +1096,63 @@ packages: '@types/vscode@1.61.0': resolution: {integrity: sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==} - '@typescript-eslint/eslint-plugin@8.49.0': - resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + '@typescript-eslint/eslint-plugin@8.50.0': + resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.49.0 + '@typescript-eslint/parser': ^8.50.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.49.0': - resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + '@typescript-eslint/parser@8.50.0': + resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.49.0': - resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + '@typescript-eslint/project-service@8.50.0': + resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.49.0': - resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} + '@typescript-eslint/scope-manager@8.50.0': + resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.49.0': - resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + '@typescript-eslint/tsconfig-utils@8.50.0': + resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.49.0': - resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + '@typescript-eslint/type-utils@8.50.0': + resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.49.0': - resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + '@typescript-eslint/types@8.50.0': + resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.49.0': - resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + '@typescript-eslint/typescript-estree@8.50.0': + resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.49.0': - resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} + '@typescript-eslint/utils@8.50.0': + resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.49.0': - resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + '@typescript-eslint/visitor-keys@8.50.0': + resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typespec/ts-http-runtime@0.3.2': @@ -1556,8 +1569,8 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -1584,8 +1597,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es-toolkit@1.42.0: - resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==} + es-toolkit@1.43.0: + resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} esbuild@0.27.1: resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} @@ -1651,6 +1664,20 @@ packages: peerDependencies: eslint: '>=5.16.0' + eslint-plugin-prettier@5.5.4: + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1671,8 +1698,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1711,6 +1738,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1901,8 +1931,8 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + iconv-lite@0.7.1: + resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -2440,6 +2470,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + prettier@3.7.4: resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} @@ -2701,6 +2735,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + table@6.9.0: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} @@ -2784,6 +2822,13 @@ packages: typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + typescript-eslint@8.50.0: + resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -3113,9 +3158,9 @@ snapshots: '@esbuild/win32-x64@0.27.1': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': dependencies: - eslint: 9.39.1 + eslint: 9.39.2 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -3150,7 +3195,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.39.1': {} + '@eslint/js@9.39.2': {} '@eslint/object-schema@2.1.7': {} @@ -3172,122 +3217,122 @@ snapshots: '@inquirer/ansi@2.0.2': {} - '@inquirer/checkbox@5.0.2(@types/node@24.10.2)': + '@inquirer/checkbox@5.0.3(@types/node@24.10.4)': dependencies: '@inquirer/ansi': 2.0.2 - '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/confirm@6.0.2(@types/node@24.10.2)': + '@inquirer/confirm@6.0.3(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/core@11.0.2(@types/node@24.10.2)': + '@inquirer/core@11.1.0(@types/node@24.10.4)': dependencies: '@inquirer/ansi': 2.0.2 '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.4) cli-width: 4.1.0 mute-stream: 3.0.0 signal-exit: 4.1.0 wrap-ansi: 9.0.2 optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/editor@5.0.2(@types/node@24.10.2)': + '@inquirer/editor@5.0.3(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/external-editor': 2.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/external-editor': 2.0.2(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/expand@5.0.2(@types/node@24.10.2)': + '@inquirer/expand@5.0.3(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/external-editor@2.0.2(@types/node@24.10.2)': + '@inquirer/external-editor@2.0.2(@types/node@24.10.4)': dependencies: chardet: 2.1.1 - iconv-lite: 0.7.0 + iconv-lite: 0.7.1 optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 '@inquirer/figures@2.0.2': {} - '@inquirer/input@5.0.2(@types/node@24.10.2)': + '@inquirer/input@5.0.3(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/number@4.0.2(@types/node@24.10.2)': + '@inquirer/number@4.0.3(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/password@5.0.2(@types/node@24.10.2)': + '@inquirer/password@5.0.3(@types/node@24.10.4)': dependencies: '@inquirer/ansi': 2.0.2 - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 - - '@inquirer/prompts@8.0.2(@types/node@24.10.2)': - dependencies: - '@inquirer/checkbox': 5.0.2(@types/node@24.10.2) - '@inquirer/confirm': 6.0.2(@types/node@24.10.2) - '@inquirer/editor': 5.0.2(@types/node@24.10.2) - '@inquirer/expand': 5.0.2(@types/node@24.10.2) - '@inquirer/input': 5.0.2(@types/node@24.10.2) - '@inquirer/number': 4.0.2(@types/node@24.10.2) - '@inquirer/password': 5.0.2(@types/node@24.10.2) - '@inquirer/rawlist': 5.0.2(@types/node@24.10.2) - '@inquirer/search': 4.0.2(@types/node@24.10.2) - '@inquirer/select': 5.0.2(@types/node@24.10.2) + '@types/node': 24.10.4 + + '@inquirer/prompts@8.1.0(@types/node@24.10.4)': + dependencies: + '@inquirer/checkbox': 5.0.3(@types/node@24.10.4) + '@inquirer/confirm': 6.0.3(@types/node@24.10.4) + '@inquirer/editor': 5.0.3(@types/node@24.10.4) + '@inquirer/expand': 5.0.3(@types/node@24.10.4) + '@inquirer/input': 5.0.3(@types/node@24.10.4) + '@inquirer/number': 4.0.3(@types/node@24.10.4) + '@inquirer/password': 5.0.3(@types/node@24.10.4) + '@inquirer/rawlist': 5.1.0(@types/node@24.10.4) + '@inquirer/search': 4.0.3(@types/node@24.10.4) + '@inquirer/select': 5.0.3(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/rawlist@5.0.2(@types/node@24.10.2)': + '@inquirer/rawlist@5.1.0(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/search@4.0.2(@types/node@24.10.2)': + '@inquirer/search@4.0.3(@types/node@24.10.4)': dependencies: - '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/select@5.0.2(@types/node@24.10.2)': + '@inquirer/select@5.0.3(@types/node@24.10.4)': dependencies: '@inquirer/ansi': 2.0.2 - '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/core': 11.1.0(@types/node@24.10.4) '@inquirer/figures': 2.0.2 - '@inquirer/type': 4.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.4) optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 - '@inquirer/type@4.0.2(@types/node@24.10.2)': + '@inquirer/type@4.0.2(@types/node@24.10.4)': optionalDependencies: - '@types/node': 24.10.2 + '@types/node': 24.10.4 '@isaacs/balanced-match@4.0.1': {} @@ -3304,16 +3349,16 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@napi-rs/cli@3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.2)': + '@napi-rs/cli@3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.4)': dependencies: - '@inquirer/prompts': 8.0.2(@types/node@24.10.2) + '@inquirer/prompts': 8.1.0(@types/node@24.10.4) '@napi-rs/cross-toolchain': 1.0.3 '@napi-rs/wasm-tools': 1.0.1 '@octokit/rest': 22.0.1 clipanion: 4.0.0-rc.4(typanion@3.14.0) colorette: 2.0.20 emnapi: 1.7.1 - es-toolkit: 1.42.0 + es-toolkit: 1.43.0 js-yaml: 4.1.1 obug: 2.1.1 semver: 7.7.3 @@ -3691,6 +3736,8 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 + '@pkgr/core@0.2.9': {} + '@rtsao/scc@1.1.0': {} '@secretlint/config-creator@10.2.2': @@ -3810,7 +3857,7 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@24.10.2': + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 @@ -3820,15 +3867,15 @@ snapshots: '@types/vscode@1.61.0': {} - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 - eslint: 9.39.1 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 + eslint: 9.39.2 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3836,56 +3883,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 debug: 4.4.3 - eslint: 9.39.1 + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.49.0': + '@typescript-eslint/scope-manager@8.50.0': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 - '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.1 + eslint: 9.39.2 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.49.0': {} + '@typescript-eslint/types@8.50.0': {} - '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -3895,20 +3942,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.50.0(eslint@9.39.2)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - eslint: 9.39.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + eslint: 9.39.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.49.0': + '@typescript-eslint/visitor-keys@8.50.0': dependencies: - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/types': 8.50.0 eslint-visitor-keys: 4.2.1 '@typespec/ts-http-runtime@0.3.2': @@ -4042,7 +4089,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 @@ -4053,7 +4100,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 @@ -4062,14 +4109,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: @@ -4077,7 +4124,7 @@ snapshots: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -4373,7 +4420,7 @@ snapshots: environment@1.1.0: {} - es-abstract@1.24.0: + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -4455,7 +4502,7 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.42.0: {} + es-toolkit@1.43.0: {} esbuild@0.27.1: optionalDependencies: @@ -4490,9 +4537,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.1): + eslint-config-prettier@10.1.8(eslint@9.39.2): dependencies: - eslint: 9.39.1 + eslint: 9.39.2 eslint-import-resolver-node@0.3.9: dependencies: @@ -4502,23 +4549,23 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - eslint: 9.39.1 + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-es@3.0.1(eslint@9.39.1): + eslint-plugin-es@3.0.1(eslint@9.39.2): dependencies: - eslint: 9.39.1 + eslint: 9.39.2 eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4527,9 +4574,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.1 + eslint: 9.39.2 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4541,22 +4588,31 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-node@11.1.0(eslint@9.39.1): + eslint-plugin-node@11.1.0(eslint@9.39.2): dependencies: - eslint: 9.39.1 - eslint-plugin-es: 3.0.1(eslint@9.39.1) + eslint: 9.39.2 + eslint-plugin-es: 3.0.1(eslint@9.39.2) eslint-utils: 2.1.0 ignore: 5.3.2 minimatch: 3.1.2 resolve: 1.22.11 semver: 6.3.1 + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.2))(eslint@9.39.2)(prettier@3.7.4): + dependencies: + eslint: 9.39.2 + prettier: 3.7.4 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.39.2) + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -4572,15 +4628,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.1: + eslint@9.39.2: dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.1 + '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 @@ -4636,6 +4692,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4846,7 +4904,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.0: + iconv-lite@0.7.1: dependencies: safer-buffer: 2.1.2 @@ -5248,14 +5306,14 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 object.values@1.2.1: dependencies: @@ -5393,6 +5451,10 @@ snapshots: prelude-ls@1.2.1: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + prettier@3.7.4: {} pump@3.0.3: @@ -5456,7 +5518,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -5658,7 +5720,7 @@ snapshots: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -5710,6 +5772,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + table@6.9.0: dependencies: ajv: 8.17.1 @@ -5824,6 +5890,17 @@ snapshots: tunnel: 0.0.6 underscore: 1.13.7 + typescript-eslint@8.50.0(eslint@9.39.2)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} uc.micro@2.1.0: {} diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 1e2bc9bb..938fd836 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -30,13 +30,12 @@ import process from "node:process"; // ### Third-party packages import escape from "escape-html"; import vscode, { - commands, Range, TextDocument, TextEditor, TextEditorRevealType, } from "vscode"; -import { CodeChatEditorServer, initServer } from "./index"; +import { CodeChatEditorServer, initServer } from "./index.js"; // ### Local packages import { @@ -50,7 +49,7 @@ import { DEBUG_ENABLED, MAX_MESSAGE_LENGTH, } from "../../../client/src/debug_enabled.mjs"; -import { ResultErrTypes } from "../../../client/src/rust-types/ResultErrTypes"; +import { ResultErrTypes } from "../../../client/src/rust-types/ResultErrTypes.js"; // Globals // ----------------------------------------------------------------------------- @@ -407,7 +406,7 @@ export const activate = (context: vscode.ExtensionContext) => { // Update the cursor and scroll position if // provided. const editor = get_text_editor(doc); - let scroll_line = current_update.scroll_position; + const scroll_line = current_update.scroll_position; if (scroll_line !== undefined && editor) { ignore_selection_change = true; const scroll_position = new vscode.Position( @@ -427,7 +426,7 @@ export const activate = (context: vscode.ExtensionContext) => { ); } - let cursor_line = current_update.cursor_position; + const cursor_line = current_update.cursor_position; if (cursor_line !== undefined && editor) { ignore_selection_change = true; const cursor_position = new vscode.Position( @@ -484,7 +483,7 @@ export const activate = (context: vscode.ExtensionContext) => { // [Built-in Commands](https://code.visualstudio.com/api/references/commands). // For now, simply respond with an OK, since the // following doesn't work. - if (false) { + /** commands .executeCommand( "vscode.open", @@ -504,7 +503,7 @@ export const activate = (context: vscode.ExtensionContext) => { ], }), ); - } + */ await sendResult(id); } break; @@ -580,6 +579,7 @@ export const deactivate = async () => { // ----------------------------------------------------------------------------- // // Format a complex data structure as a string when in debug mode. +/*eslint-disable-next-line @typescript-eslint/no-explicit-any */ const format_struct = (complex_data_structure: any): string => DEBUG_ENABLED ? JSON.stringify( @@ -765,6 +765,7 @@ const get_text_editor = (doc: TextDocument): TextEditor | undefined => { } }; +/*eslint-disable-next-line @typescript-eslint/no-explicit-any */ const console_log = (...args: any) => { if (DEBUG_ENABLED) { console.log(...args); diff --git a/extensions/VSCode/tsconfig.json b/extensions/VSCode/tsconfig.json index 10de38ac..d2e1caa0 100644 --- a/extensions/VSCode/tsconfig.json +++ b/extensions/VSCode/tsconfig.json @@ -26,5 +26,5 @@ "rootDirs": ["src", "../../client/src/*"], "allowJs": true }, - "exclude": ["node_modules", ".vscode-test", "out", "server"] + "exclude": ["node_modules", ".vscode-test", "out", "server", "eslint.config.js"] } diff --git a/server/Cargo.lock b/server/Cargo.lock index 6261210e..9ea76c0f 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -746,7 +746,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codechat-editor-server" -version = "0.1.44" +version = "0.1.46" dependencies = [ "actix-files", "actix-http", @@ -768,13 +768,13 @@ dependencies = [ "futures", "futures-util", "htmd", - "html5ever 0.36.1", + "html5ever", "imara-diff", "indoc", "lazy_static", "log", "log4rs", - "markup5ever_rcdom 0.36.0+unofficial", + "markup5ever_rcdom", "mime", "mime_guess", "minreq", @@ -783,7 +783,7 @@ dependencies = [ "path-slash", "pest", "pest_derive", - "phf 0.13.1", + "phf", "predicates", "pretty_assertions", "pulldown-cmark 0.13.0", @@ -791,6 +791,7 @@ dependencies = [ "regex", "serde", "serde_json", + "test_utils", "thirtyfour", "thiserror 2.0.17", "tokio", @@ -1581,22 +1582,11 @@ dependencies = [ [[package]] name = "htmd" version = "0.5.0" -source = "git+https://github.com/bjones1/htmd.git?branch=math-support#af6bc129874d33eb7f4d7fb6f0a39b04c668b2f5" +source = "git+https://github.com/bjones1/htmd.git?branch=dom-interface#a8a3c34143ad5641baa784e43bc73668dbbebc2f" dependencies = [ - "html5ever 0.35.0", - "markup5ever_rcdom 0.35.0+unofficial", - "phf 0.13.1", -] - -[[package]] -name = "html5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" -dependencies = [ - "log", - "markup5ever 0.35.0", - "match_token", + "html5ever", + "markup5ever_rcdom", + "phf", ] [[package]] @@ -1606,7 +1596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6452c4751a24e1b99c3260d505eaeee76a050573e61f30ac2c924ddc7236f01e" dependencies = [ "log", - "markup5ever 0.36.1", + "markup5ever", ] [[package]] @@ -2126,9 +2116,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b484ba8d4f775eeca644c452a56650e544bf7e617f1d170fe7298122ead5222" +checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" dependencies = [ "zlib-rs", ] @@ -2247,17 +2237,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "markup5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" -dependencies = [ - "log", - "tendril", - "web_atoms 0.1.3", -] - [[package]] name = "markup5ever" version = "0.36.1" @@ -2266,19 +2245,7 @@ checksum = "6c3294c4d74d0742910f8c7b466f44dda9eb2d5742c1e430138df290a1e8451c" dependencies = [ "log", "tendril", - "web_atoms 0.2.0", -] - -[[package]] -name = "markup5ever_rcdom" -version = "0.35.0+unofficial" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" -dependencies = [ - "html5ever 0.35.0", - "markup5ever 0.35.0", - "tendril", - "xml5ever 0.35.0", + "web_atoms", ] [[package]] @@ -2287,21 +2254,10 @@ version = "0.36.0+unofficial" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e5fc8802e8797c0dfdd2ce5c21aa0aee21abbc7b3b18559100651b3352a7b63" dependencies = [ - "html5ever 0.36.1", - "markup5ever 0.36.1", + "html5ever", + "markup5ever", "tendril", - "xml5ever 0.36.1", -] - -[[package]] -name = "match_token" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", + "xml5ever", ] [[package]] @@ -2605,15 +2561,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared 0.11.3", -] - [[package]] name = "phf" version = "0.13.1" @@ -2621,38 +2568,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", - "phf_shared 0.13.1", + "phf_shared", "serde", ] -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", + "phf_generator", + "phf_shared", ] [[package]] @@ -2662,7 +2589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared 0.13.1", + "phf_shared", ] [[package]] @@ -2671,22 +2598,13 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", "syn 2.0.111", ] -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "phf_shared" version = "0.13.1" @@ -3053,9 +2971,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ "base64", "bytes", @@ -3501,19 +3419,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -3522,31 +3427,19 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.13.1", + "phf_shared", "precomputed-hash", "serde", ] -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "string_cache_codegen" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -3675,6 +3568,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "test_utils" +version = "0.1.0" +dependencies = [ + "assert_fs", + "assertables", + "log", +] + [[package]] name = "thirtyfour" version = "0.36.1" @@ -3863,7 +3765,7 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf 0.13.1", + "phf", "pin-project-lite", "postgres-protocol", "postgres-types", @@ -4354,28 +4256,16 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web_atoms" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" -dependencies = [ - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache 0.8.9", - "string_cache_codegen 0.5.4", -] - [[package]] name = "web_atoms" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acd0c322f146d0f8aad130ce6c187953889359584497dac6561204c8e17bb43d" dependencies = [ - "phf 0.13.1", - "phf_codegen 0.13.1", - "string_cache 0.9.0", - "string_cache_codegen 0.6.1", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", ] [[package]] @@ -4841,16 +4731,6 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" -[[package]] -name = "xml5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" -dependencies = [ - "log", - "markup5ever 0.35.0", -] - [[package]] name = "xml5ever" version = "0.36.1" @@ -4858,7 +4738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f57dd51b88a4b9f99f9b55b136abb86210629d61c48117ddb87f567e51e66be7" dependencies = [ "log", - "markup5ever 0.36.1", + "markup5ever", ] [[package]] @@ -5009,9 +4889,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7" +checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" [[package]] name = "zopfli" diff --git a/server/Cargo.toml b/server/Cargo.toml index 96a6138a..d8fe63e5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -32,7 +32,7 @@ license = "GPL-3.0-only" name = "codechat-editor-server" readme = "../README.md" repository = "https://github.com/bjones1/CodeChat_Editor" -version = "0.1.44" +version = "0.1.46" # This library allows other packages to use core CodeChat Editor features. [lib] @@ -46,9 +46,6 @@ name = "code_chat_editor" # Uncomment this to show an explanation of the lexing process. #default = ["lexer_explain"] lexer_explain = [] -# The `test` configuration option (i.e., `#[cfg(test)]`) applies only to -# functional tests. This feature applies only to integration tests. -int_tests = ["assert_fs", "assertables", "futures"] # Dependencies # ------------------------------------------------------------------------------ @@ -62,20 +59,13 @@ actix-web = "4" actix-web-httpauth = "0.8.2" actix-ws = "0.3.0" anyhow = "1.0.100" -# This is used for integration testing. However, since integration tests can't -# access crates in `dev-dependencies`, they're here behind the appropriate -# feature flag. -assert_fs = { version = "1", optional = true } -assertables = { version = "9", optional = true } bytes = { version = "1", features = ["serde"] } chrono = "0.4" clap = { version = "4.5.19", features = ["derive"] } dprint-plugin-markdown = { git = "https://github.com/bjones1/dprint-plugin-markdown.git", branch = "all-fixes", version = "0.20.0" } dunce = "1.0.5" -# This is also for integration testing. -futures = { version = "0.3.31", optional = true } futures-util = "0.3.29" -htmd = { git = "https://github.com/bjones1/htmd.git", branch = "math-support", version = "0.5" } +htmd = { git = "https://github.com/bjones1/htmd.git", branch = "dom-interface", version = "0.5" } html5ever = "0.36.1" imara-diff = { version = "0.2", features = [] } indoc = "2.0.5" @@ -99,6 +89,7 @@ rand = "0.9.2" regex = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" +test_utils = { path = "../test_utils" } thiserror = "2.0.12" tokio = { version = "1", features = ["full"] } tokio-postgres = { version = "0.7", features = ["with-chrono-0_4"] } @@ -117,6 +108,7 @@ actix-http = "3.9.0" assert_cmd = "2.0.16" assert_fs = "1" assertables = "9" +futures = "0.3.31" predicates = "3.1.2" pretty_assertions = "1.4.1" thirtyfour = { git = "https://github.com/bjones1/thirtyfour.git", branch = "selenium_manager" } diff --git a/server/src/ide/filewatcher.rs b/server/src/ide/filewatcher.rs index 9509fac9..2ff42faf 100644 --- a/server/src/ide/filewatcher.rs +++ b/server/src/ide/filewatcher.rs @@ -518,8 +518,8 @@ async fn processing_task( // Close the file if it can't be read as // Unicode text. - if read_ret.is_err() { - error!("Unable to read '{}': {}", cfp.to_string_lossy(), read_ret.unwrap_err()); + if let Err(e) = read_ret { + error!("Unable to read '{}': {e}", cfp.to_string_lossy()); break 'task; } @@ -724,12 +724,10 @@ mod tests { use super::FW; use crate::{ - cast, prep_test_dir, processing::{ CodeChatForWeb, CodeMirror, CodeMirrorDiffable, SourceFileMetadata, TranslationResults, source_to_codechat_for_web, }, - test_utils::{check_logger_errors, configure_testing_logger}, webserver::{ EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, INITIAL_IDE_MESSAGE_ID, INITIAL_MESSAGE_ID, IdeType, MESSAGE_ID_INCREMENT, @@ -737,6 +735,10 @@ mod tests { configure_app, drop_leading_slash, make_app_data, send_response, set_root_path, }, }; + use test_utils::{ + cast, prep_test_dir, + test_utils::{check_logger_errors, configure_testing_logger}, + }; async fn get_websocket_queues( // A path to the temporary directory where the source file is located. diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index a3d1b141..7a69f2fa 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -52,18 +52,20 @@ use crate::webserver::{ INITIAL_MESSAGE_ID, IdeType, MESSAGE_ID_INCREMENT, ResultErrTypes, }; use crate::{ - cast, processing::{ CodeChatForWeb, CodeMirror, CodeMirrorDiff, CodeMirrorDiffable, CodeMirrorDocBlock, CodeMirrorDocBlockTransaction, SourceFileMetadata, StringDiff, }, - test_utils::{_prep_test_dir, check_logger_errors, configure_testing_logger}, webserver::{ResultOkTypes, UpdateMessageContents, drop_leading_slash}, }; use crate::{ translation::{EolType, find_eol_type}, webserver::main, }; +use test_utils::{ + cast, + test_utils::{_prep_test_dir, check_logger_errors, configure_testing_logger}, +}; // Globals // ------- @@ -225,7 +227,7 @@ async fn _prep_test( /// `_prep_test` would give the wrong name. macro_rules! prep_test { ($connection_id: ident) => {{ - use crate::function_name; + use test_utils::function_name; _prep_test($connection_id, function_name!()) }}; } diff --git a/server/src/lexer/tests.rs b/server/src/lexer/tests.rs index a3f89738..a27c600d 100644 --- a/server/src/lexer/tests.rs +++ b/server/src/lexer/tests.rs @@ -20,9 +20,9 @@ // ------- use super::supported_languages::get_language_lexer_vec; use super::{CodeDocBlock, DocBlock, compile_lexers, source_lexer}; -use crate::test_utils::stringit; use indoc::indoc; use pretty_assertions::assert_eq; +use test_utils::test_utils::stringit; // Utilities // --------- diff --git a/server/src/lib.rs b/server/src/lib.rs index 9e336c1e..7e178fc2 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -25,8 +25,3 @@ pub mod lexer; pub mod processing; pub mod translation; pub mod webserver; - -#[cfg(any(feature = "int_tests", test))] -pub mod test_utils; -#[cfg(any(feature = "int_tests", test))] -pub mod testing_logger; diff --git a/server/src/processing.rs b/server/src/processing.rs index 25849bfd..057e875d 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -452,10 +452,10 @@ pub fn codechat_for_web_to_source( } // Translate the HTML document to Markdown. let converter = HtmlToMarkdownWrapped::new(); - let dry_html = - dehydrate_html(&code_mirror.doc).map_err(CodechatForWebToSourceError::ParseFailed)?; + let tree = html_to_tree(&code_mirror.doc)?; + dehydrating_walk_node(&tree); return converter - .convert(&dry_html) + .convert(&tree) .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed); } let code_doc_block_vec_html = code_mirror_to_code_doc_blocks(code_mirror); @@ -576,17 +576,37 @@ impl HtmlToMarkdownWrapped { self.word_wrap_config.line_width = line_width as u32; } - fn convert(&self, html: &str) -> Result { - let converted = self.html_to_markdown.convert(html)?; + /// Convert one item in a stream of HTML to markdown. The HTML must be start at the root, not continue a previous incomplete section of the DOM. + fn next(&self, tree: &Rc) -> Result { + let converted = self.html_to_markdown.tree_to_markdown(tree)?; Ok( format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None))? // A return value of `None` means the text was unchanged or // ignored (by an // [ignoreFileDirective](https://dprint.dev/plugins/markdown/config/)). // Simply return the unchanged text in this case. - .unwrap_or_else(|| html.to_string()), + .unwrap_or_else(|| converted.to_string()), ) } + + fn last(&self) -> Result { + let converted = self.html_to_markdown.finalize_conversion()?; + Ok( + format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None))? + // A return value of `None` means the text was unchanged or + // ignored (by an + // [ignoreFileDirective](https://dprint.dev/plugins/markdown/config/)). + // Simply return the unchanged text in this case. + .unwrap_or_else(|| converted.to_string()), + ) + } + + /// Convert HTML to markdown. + fn convert(&self, tree: &Rc) -> Result { + let mut converted = self.next(tree)?; + converted.push_str(&self.last()?); + Ok(converted) + } } // Transform HTML in doc blocks to Markdown. @@ -596,7 +616,8 @@ fn doc_block_html_to_markdown( let mut converter = HtmlToMarkdownWrapped::new(); for code_doc_block in &mut code_doc_block_vec { if let CodeDocBlock::DocBlock(doc_block) = code_doc_block { - let contents = dehydrate_html(&doc_block.contents)?; + let tree = html_to_tree(&doc_block.contents)?; + dehydrating_walk_node(&tree); // Compute a line wrap width based on the current indent. Set a // minimum of half the line wrap width, to prevent ridiculous @@ -612,10 +633,18 @@ fn doc_block_html_to_markdown( WORD_WRAP_COLUMN, ), )); - doc_block.contents = converter.convert(&contents)?; + doc_block.contents = converter.next(&tree)?; } } + // Output the finalized conversion. + if let Some(code_doc_block) = code_doc_block_vec.last_mut() + && let CodeDocBlock::DocBlock(doc_block) = code_doc_block + { + let last = converter.last()?; + doc_block.contents.push_str(&last); + } + Ok(code_doc_block_vec) } @@ -1005,11 +1034,8 @@ fn markdown_to_html(markdown: &str) -> String { html_output } -// A framework to transform HTML by parsing it to a DOM tree, walking the tree, then serializing the tree back to an HTML string. -fn transform_html)>(html_in: &str, transform: T) -> io::Result { - // The approach: transform the HTML to a DOM tree, then walk the three to apply these transformations. - // - // First, parse it to a DOM. +// Use html5ever to parse a string containing HTML to a DOM tree. +fn html_to_tree(html: &str) -> io::Result> { let dom = parse_document( RcDom::default(), ParseOpts { @@ -1021,10 +1047,22 @@ fn transform_html)>(html_in: &str, transform: T) -> io::Resul }, ) .from_utf8() - .read_from(&mut html_in.as_bytes())?; + .read_from(&mut html.as_bytes())?; - // Walk and transform the DOM. - transform(dom.document.clone()); + // TODO: should we report parse errors? If so, how and where? + /* + if let Some(err) = dom.errors.borrow().first() { + //return Err(io::Error::other(err.to_string())); + } + */ + + Ok(dom.document) +} + +// A framework to transform HTML by parsing it to a DOM tree, walking the tree, then serializing the tree back to an HTML string. +fn transform_html)>(html: &str, transform: T) -> io::Result { + let tree = html_to_tree(html)?; + transform(tree.clone()); // Serialize the transformed DOM back to a string. let so = SerializeOpts { @@ -1040,8 +1078,7 @@ fn transform_html)>(html_in: &str, transform: T) -> io::Resul // ... <-- element 1 // // ``` - let body = dom.document.children.borrow()[0].children.borrow()[1].clone(); - //println!("{:#?}", body); + let body = tree.children.borrow()[0].children.borrow()[1].clone(); serialize(&mut bytes, &SerializableHandle::from(body.clone()), so)?; let html_out = String::from_utf8(bytes).map_err(io::Error::other)?; @@ -1105,6 +1142,7 @@ fn hydrating_walk_node(node: Rc) { // Replace the child if we found a replacement; otherwise, walk it. if let Some(replacement_child) = possible_replacement_child { + replacement_child.parent.set(Some(Rc::downgrade(&node))); *child = replacement_child; } else { hydrating_walk_node(child.clone()); @@ -1162,6 +1200,7 @@ fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { template_contents: RefCell::new(None), mathml_annotation_xml_integration_point: false, }); + delimited_text_node.parent.set(Some(Rc::downgrade(&span))); span.children.borrow_mut().push(delimited_text_node); Some(span) } else { @@ -1172,11 +1211,7 @@ fn replace_math_node(child: &Rc, is_hydrate: bool) -> Option> { } } -fn dehydrate_html(html: &str) -> io::Result { - transform_html(html, dehydrating_walk_node) -} - -fn dehydrating_walk_node(node: Rc) { +fn dehydrating_walk_node(node: &Rc) { for child in node.children.borrow_mut().iter_mut() { // Look for a custom element tag let possible_replacement_child = if let Some(child_name) = get_node_tag_name(child) @@ -1208,6 +1243,7 @@ fn dehydrating_walk_node(node: Rc) { template_contents: RefCell::new(None), mathml_annotation_xml_integration_point: false, }); + code.parent.set(Some(Rc::downgrade(&pre))); code.children.borrow_mut().push(text_child.clone()); pre.children.borrow_mut().push(code); Some(pre) @@ -1219,7 +1255,7 @@ fn dehydrating_walk_node(node: Rc) { if let Some(replacement_child) = possible_replacement_child { *child = replacement_child; } else { - dehydrating_walk_node(child.clone()); + dehydrating_walk_node(child); } } } diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index 653ebf29..10abb5de 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -21,10 +21,11 @@ // ------- // // ### Standard library -use std::{path::PathBuf, str::FromStr}; +use std::{io, path::PathBuf, rc::Rc, str::FromStr}; // ### Third-party use indoc::indoc; +use markup5ever_rcdom::Node; use predicates::prelude::predicate::str; use pretty_assertions::assert_eq; @@ -34,22 +35,20 @@ use super::{ TranslationResults, find_path_to_toc, }; use crate::{ - cast, lexer::{ CodeDocBlock, DocBlock, compile_lexers, supported_languages::{MARKDOWN_MODE, get_language_lexer_vec}, }, - prep_test_dir, processing::{ CodeDocBlockVecToSourceError, CodeMirrorDiffable, CodeMirrorDocBlockDelete, CodeMirrorDocBlockTransaction, CodeMirrorDocBlockUpdate, CodechatForWebToSourceError, HtmlToMarkdownWrapped, SourceToCodeChatForWebError, byte_index_of, code_doc_block_vec_to_source, code_mirror_to_code_doc_blocks, codechat_for_web_to_source, - dehydrate_html, diff_code_mirror_doc_blocks, diff_str, hydrate_html, markdown_to_html, - source_to_codechat_for_web, + dehydrating_walk_node, diff_code_mirror_doc_blocks, diff_str, html_to_tree, hydrate_html, + markdown_to_html, source_to_codechat_for_web, }, - test_utils::stringit, }; +use test_utils::{cast, prep_test_dir, test_utils::stringit}; // Utilities // --------- @@ -1303,6 +1302,26 @@ fn test_hydrate_html_1() { "# ) ); + + assert_eq!( + hydrate_html(&markdown_to_html("1. foo\u{a0}\n2. bar \n3. baz ")).unwrap(), + indoc!( + " +
    +
  1. foo 
  2. +
  3. bar
  4. +
  5. baz
  6. +
+ " + ) + ); +} + +fn dehydrate_html(html: &str) -> io::Result> { + let tree = html_to_tree(html)?; + dehydrating_walk_node(&tree); + //println!("{:#?}", tree); + Ok(tree) } #[test] @@ -1313,10 +1332,10 @@ fn test_dehydrate_html_1() { .convert( &dehydrate_html(indoc!( " - flowchart LR - start --> stop - - " + flowchart LR + start --> stop + + " )) .unwrap() ) @@ -1336,11 +1355,11 @@ fn test_dehydrate_html_1() { .convert( &dehydrate_html(indoc!( " - digraph { - start -> stop - } - - " + digraph { + start -> stop + } + + " )) .unwrap() ) @@ -1380,4 +1399,20 @@ fn test_dehydrate_html_1() { " ) ); + + assert_eq!( + converter + .convert( + &dehydrate_html(indoc!( + " +
    +
  1. foo 
  2. +
+ " + )) + .unwrap() + ) + .unwrap(), + "1. foo\u{a0}\n" + ); } diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 17b870c9..32d05a35 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -1472,10 +1472,7 @@ pub fn configure_logger(level: LevelFilter) -> Result<(), Box c, - Err(err) => return Err(err.into()), - }; + let mut config = load_config_file(&config_file, Default::default())?; config.root_mut().set_level(level); log4rs::init_config(config)?; Ok(()) diff --git a/server/src/webserver/tests.rs b/server/src/webserver/tests.rs index 9cc41e2c..eccd54af 100644 --- a/server/src/webserver/tests.rs +++ b/server/src/webserver/tests.rs @@ -29,11 +29,8 @@ use assertables::{assert_ends_with, assert_not_contains, assert_starts_with}; // ### Local use super::{path_to_url, url_to_path}; -use crate::prep_test_dir; -use crate::{ - cast, - ide::{filewatcher::FILEWATCHER_PATH_PREFIX, vscode::tests::IP_PORT}, -}; +use crate::ide::{filewatcher::FILEWATCHER_PATH_PREFIX, vscode::tests::IP_PORT}; +use test_utils::{cast, prep_test_dir}; // Support functions // ----------------- diff --git a/server/tests/fixtures/overall/overall_core/test_client/test.py b/server/tests/fixtures/overall_1/test_client/test.py similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_client/test.py rename to server/tests/fixtures/overall_1/test_client/test.py diff --git a/server/tests/fixtures/overall/overall_core/test_client/toc.md b/server/tests/fixtures/overall_1/test_client/toc.md similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_client/toc.md rename to server/tests/fixtures/overall_1/test_client/toc.md diff --git a/server/tests/fixtures/overall/overall_core/test_4/test.py b/server/tests/fixtures/overall_1/test_client_updates/test.py similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_4/test.py rename to server/tests/fixtures/overall_1/test_client_updates/test.py diff --git a/server/tests/fixtures/overall/overall_core/test_client_updates/toc.md b/server/tests/fixtures/overall_1/test_client_updates/toc.md similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_client_updates/toc.md rename to server/tests/fixtures/overall_1/test_client_updates/toc.md diff --git a/server/tests/fixtures/overall/overall_core/test_server/test.md b/server/tests/fixtures/overall_1/test_server/test.md similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_server/test.md rename to server/tests/fixtures/overall_1/test_server/test.md diff --git a/server/tests/fixtures/overall/overall_core/test_server/test.pdf b/server/tests/fixtures/overall_1/test_server/test.pdf similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_server/test.pdf rename to server/tests/fixtures/overall_1/test_server/test.pdf diff --git a/server/tests/fixtures/overall/overall_core/test_5/test.py b/server/tests/fixtures/overall_1/test_server/test.py similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_5/test.py rename to server/tests/fixtures/overall_1/test_server/test.py diff --git a/server/tests/fixtures/overall/overall_core/test_server/test.txt b/server/tests/fixtures/overall_1/test_server/test.txt similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_server/test.txt rename to server/tests/fixtures/overall_1/test_server/test.txt diff --git a/server/tests/fixtures/overall/overall_core/test_server/toc.md b/server/tests/fixtures/overall_1/test_server/toc.md similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_server/toc.md rename to server/tests/fixtures/overall_1/test_server/toc.md diff --git a/server/tests/fixtures/overall/overall_core/test_client_updates/test.py b/server/tests/fixtures/overall_2/test_4/test.py similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_client_updates/test.py rename to server/tests/fixtures/overall_2/test_4/test.py diff --git a/server/tests/fixtures/overall/overall_core/test_server/test.py b/server/tests/fixtures/overall_2/test_5/test.py similarity index 100% rename from server/tests/fixtures/overall/overall_core/test_server/test.py rename to server/tests/fixtures/overall_2/test_5/test.py diff --git a/server/tests/fixtures/overall_2/test_6/test.md b/server/tests/fixtures/overall_2/test_6/test.md new file mode 100644 index 00000000..3b1386f5 --- /dev/null +++ b/server/tests/fixtures/overall_2/test_6/test.md @@ -0,0 +1,3 @@ +The contents of this file don't matter -- tests will supply the content, +instead of loading it from disk. However, it does need to exist for +`canonicalize` to find the correct path to this file. diff --git a/server/tests/overall.rs b/server/tests/overall.rs deleted file mode 100644 index 01df3dbf..00000000 --- a/server/tests/overall.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2025 Bryan A. Jones. -// -// This file is part of the CodeChat Editor. The CodeChat Editor is free -// software: you can redistribute it and/or modify it under the terms of the GNU -// General Public License as published by the Free Software Foundation, either -// version 3 of the License, or (at your option) any later version. -// -// The CodeChat Editor is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -// details. -// -// You should have received a copy of the GNU General Public License along with -// the CodeChat Editor. If not, see -// [http://www.gnu.org/licenses](http://www.gnu.org/licenses). -/// `overall.rs` - test the overall system -/// ====================================== -#[cfg(feature = "int_tests")] -mod overall_core; diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_1.rs similarity index 56% rename from server/tests/overall_core/mod.rs rename to server/tests/overall_1.rs index d3b85a5d..aeff98f1 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_1.rs @@ -19,41 +19,15 @@ /// These are functional tests of the overall system, performed by attaching a /// testing IDE to generate commands then observe results, along with a browser /// tester. -/// -/// Some subtleties of this approach: development dependencies aren't available -/// to integration tests. Therefore, this crate's `Cargo.toml` file includes the -/// `int_tests` feature, which enables crates needed only for integration -/// testing, while keeping these out of the final binary when compiling for -/// production. This means that the same crate appears both in -/// `dev-dependencies` and in `dependencies`, so it's available for both unit -/// tests and integration tests. In addition, any code used in integration tests -/// must be gated on the `int_tests` feature, since this code fails to compile -/// without that feature's crates enabled. Tests are implemented here, then -/// `use`d in `overall.rs`, so that a single `#[cfg(feature = "int_tests")]` -/// statement there gates everything in this file. See the -/// [test docs](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests) -/// for the correct file and directory names. -/// -/// A second challenge revolves around the lack of an async `Drop` trait: the -/// web driver server should be started before any test, left running during all -/// tests, then terminated as the test program exits. The web driver must be -/// initialized before a test then stopped at the end of that test. Both are -/// ideal for this missing Drop trait. As a workaround: -/// -/// * The web driver server relies on the C `atexit` call to stop the server. -/// However, when tests fail, this doesn't get called, leaving the server -/// running. This causes the server to fail to start on the next test run, -/// since it's still running. Therefore, errors when starting the web driver -/// server are ignored by design. -/// * Tests are run in an async block, and any panics produced inside it are -/// caught using `catch_unwind()`. The driver is shut down before returning an -/// error due to the panic. +// Modules +// ----------------------------------------------------------------------------- +mod overall_common; + // Imports // ----------------------------------------------------------------------------- // // ### Standard library use std::{ - collections::HashMap, env, error::Error, panic::AssertUnwindSafe, @@ -68,17 +42,19 @@ use futures::FutureExt; use indoc::indoc; use pretty_assertions::assert_eq; use thirtyfour::{ - By, ChromiumLikeCapabilities, DesiredCapabilities, Key, WebDriver, WebElement, - error::WebDriverError, start_webdriver_process, + By, ChromiumLikeCapabilities, DesiredCapabilities, Key, WebDriver, error::WebDriverError, + start_webdriver_process, }; use tokio::time::sleep; // ### Local +use crate::overall_common::{ + ExpectedMessages, TIMEOUT, assert_no_more_messages, get_version, goto_line, perform_loadfile, + select_codechat_iframe, +}; use code_chat_editor::{ - cast, ide::CodeChatEditorServer, lexer::supported_languages::MARKDOWN_MODE, - prep_test_dir, processing::{ CodeChatForWeb, CodeMirrorDiff, CodeMirrorDiffable, SourceFileMetadata, StringDiff, }, @@ -87,330 +63,8 @@ use code_chat_editor::{ ResultOkTypes, UpdateMessageContents, set_root_path, }, }; +use test_utils::{cast, prep_test_dir}; -// Utilities -// ----------------------------------------------------------------------------- -// -// Not all messages produced by the server are ordered. To accommodate -// out-of-order messages, this class provides a way to `insert` expected -// messages, then wait until they're all be received (`assert_all_messages`). -struct ExpectedMessages(HashMap); - -impl ExpectedMessages { - fn new() -> ExpectedMessages { - ExpectedMessages(HashMap::new()) - } - - fn insert(&mut self, editor_message: EditorMessage) { - assert!( - self.0 - .insert(editor_message.id as i64, editor_message.message) - .is_none() - ); - } - - fn check(&mut self, editor_message: EditorMessage) { - if let Some(editor_message_contents) = self.0.remove(&(editor_message.id as i64)) { - assert_eq!(editor_message.message, editor_message_contents); - } else { - panic!( - "Message not found: looked for \n{:#?}\nin:\n{:#?}", - editor_message, self.0 - ); - } - } - - async fn _assert_message(&mut self, codechat_server: &CodeChatEditorServer, timeout: Duration) { - self.check(codechat_server.get_message_timeout(timeout).await.unwrap()); - } - - async fn assert_all_messages( - &mut self, - codechat_server: &CodeChatEditorServer, - timeout: Duration, - ) { - while !self.0.is_empty() { - self.check(codechat_server.get_message_timeout(timeout).await.unwrap()); - } - } -} - -// Time to wait for `ExpectedMessages`. -const TIMEOUT: Duration = Duration::from_millis(2000); - -// ### Test harness -// -// A test harness. It runs the webdriver, the Server, opens the Client, then -// runs provided tests. After testing finishes, it cleans up (handling panics -// properly). -// -// The goal was to pass the harness a function which runs the tests. This -// currently doesn't work, due to problems with lifetimes (see comments). So, -// implement this as a macro instead (kludge!). -macro_rules! harness { - // The name of the test function to call inside the harness. - ($func: ident) => { - pub async fn harness< - 'a, - F: FnOnce(CodeChatEditorServer, &'a WebDriver, &'a Path) -> Fut, - Fut: Future>, - >( - // The function which performs tests using thirtyfour. TODO: not - // used. - _f: F, - // The output from calling `prep_test_dir!()`. - prep_test_dir: (TempDir, PathBuf), - ) -> Result<(), Box> { - let (temp_dir, test_dir) = prep_test_dir; - // The logger gets configured by (I think) - // `start_webdriver_process`, which delegates to `selenium-manager`. - // Set logging level here. - unsafe { env::set_var("RUST_LOG", "debug") }; - // Start the webdriver. - let server_url = "http://localhost:4444"; - let mut caps = DesiredCapabilities::chrome(); - // Ensure the screen is wide enough for an 80-character line, used - // to word wrapping test in `test_client_updates`. Otherwise, this - // test send the End key to go to the end of the line...but it's not - // the end of the full line on a narrow screen. - caps.add_arg("--window-size=1920,768")?; - caps.add_arg("--headless")?; - // On Ubuntu CI, avoid failures, probably due to running Chrome as - // root. - #[cfg(target_os = "linux")] - if env::var("CI") == Ok("true".to_string()) { - caps.add_arg("--disable-gpu")?; - caps.add_arg("--no-sandbox")?; - } - if let Err(err) = start_webdriver_process(server_url, &caps, true) { - // Often, the "failure" is that the webdriver is already - // running. - eprintln!("Failed to start the webdriver process: {err:#?}"); - } - // Wait for the driver to start up. - sleep(Duration::from_millis(500)).await; - let driver = WebDriver::new(server_url, caps).await?; - let driver_clone = driver.clone(); - let driver_ref = &driver_clone; - - // Run the test inside an async, so we can shut down the driver - // before returning an error. Mark the function as unwind safe. - // though I'm not certain this is correct. Hopefully, it's good - // enough for testing. - let ret = AssertUnwindSafe(async move { - // ### Setup - let p = env::current_exe().unwrap().parent().unwrap().join("../.."); - set_root_path(Some(&p)).unwrap(); - let codechat_server = CodeChatEditorServer::new().unwrap(); - - // Get the resulting web page text. - let opened_id = codechat_server.send_message_opened(true).await.unwrap(); - pretty_assertions::assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: opened_id, - message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) - } - ); - let em_html = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); - codechat_server.send_result(em_html.id, None).await.unwrap(); - - // Parse out the address to use. - let client_html = cast!(&em_html.message, EditorMessageContents::ClientHtml); - let find_str = "