diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a840d41a..06555318 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Rust", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", + "image": "mcr.microsoft.com/devcontainers/rust:latest", // Use 'mounts' to make the cargo cache persistent in a Docker Volume. // "mounts": [ // { diff --git a/CHANGELOG.md b/CHANGELOG.md index 7896b251..c87e8502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,240 +17,261 @@ CodeChat Editor. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). Changelog -========= +================================================================================ [Github master](https://github.com/bjones1/CodeChat_Editor) ------------------------------------------------------------ - -* No changes. +-------------------------------------------------------------------------------- + +* Drag and drop of images creates a mess; disable drop and drop for this reason. +* Send sync data when doc blocks receive focus. +* Improve error handling. +* Improve method used to keep Client and IDE contents in sync. +* Correctly sync when IDE files are updated when editing in the Client. +* Fix data corruption bug when creating new doc blocks in the Client -- + whitespace was lost. +* Update line numbering in the Client when word wraps occur in a doc block. +* Avoid unnecessary saves from the Client just after receiving new data. +* Reduce delay between Client edits and IDE updates. +* Increase time errors messages are displayed in the Client. + +Version 0.1.41 -- 2025-Nov-17 +-------------------------------------------------------------------------------- + +* Math now renders in the active doc block, as shown below. +* Math is now protected from accidental edits in the Client: + ![](docs/changelog_0.1.41-math.png) +* Improved synchronization between the cursor and scroll position of the Client + and IDE. +* Removed accidental debug output. +* Remove unnecessary TinyMCE plugins. Version 0.1.40 -- 2025-Nov-11 ------------------------------ +-------------------------------------------------------------------------------- -* File watcher now accepts new files from the Client. -* Improved translation from HTML to Markdown. -* Build correct binary for Mac M1. -* Add CodeMirror support for SQL and YAML. +* File watcher now accepts new files from the Client. +* Improved translation from HTML to Markdown. +* Build correct binary for Mac M1. +* Add CodeMirror support for SQL and YAML. Version 0.1.39 -- 2025-Oct-23 ------------------------------ +-------------------------------------------------------------------------------- -* Scroll viewport to center on cursor movement. -* Significant improvements to test framework. -* Improved build framework. -* Refactor IDE interface back into the Server. +* Scroll viewport to center on cursor movement. +* Significant improvements to test framework. +* Improved build framework. +* Refactor IDE interface back into the Server. Version 0.1.38 -- 2025-Sep-24 ------------------------------ +-------------------------------------------------------------------------------- -* Automatically find a free port for the Client, instead of requiring the user - to specify it in the VSCode settings. -* Remove unused VSCode settings. -* Update code in the IDE when changes are made to the indent in the Client. -* Enable spell checking in doc blocks. Note that the VSCode browser doesn't - support spell checking. -* Improved build process. +* Automatically find a free port for the Client, instead of requiring the user + to specify it in the VSCode settings. +* Remove unused VSCode settings. +* Update code in the IDE when changes are made to the indent in the Client. +* Enable spell checking in doc blocks. Note that the VSCode browser doesn't + support spell checking. +* Improved build process. Version 0.1.37 -- 2025-Sep-22 ------------------------------ +-------------------------------------------------------------------------------- -* Fix: avoid loading the same file twice in a row when the user clicks on a - link in the Client. -* Improve VSCode connection with the Server. +* Fix: avoid loading the same file twice in a row when the user clicks on a link + in the Client. +* Improve VSCode connection with the Server. Version 0.1.36 -- 2025-Sep-17 ------------------------------ +-------------------------------------------------------------------------------- -* The system continues to work after a PC goes to sleep then wakes back up; - communication within previous versions fails after a sleep/wake up cycle. -* Communicate between the VSCode extension and the Server using queues instead - of a websocket and a queue. -* Remove the HTML editor's toolbar, to free up more space on the screen. -* Add a code formatting button to the quick toolbar which appears when tex is - selected in the HTML editor. -* Remove the File entry from the HTML editor's main menu. +* The system continues to work after a PC goes to sleep then wakes back up; + communication within previous versions fails after a sleep/wake up cycle. +* Communicate between the VSCode extension and the Server using queues instead + of a websocket and a queue. +* Remove the HTML editor's toolbar, to free up more space on the screen. +* Add a code formatting button to the quick toolbar which appears when tex is + selected in the HTML editor. +* Remove the File entry from the HTML editor's main menu. Version 0.1.35 -- 2025-Sep-12 ------------------------------ +-------------------------------------------------------------------------------- -* Embed the CodeChat Editor Server inside the VSCode extension, rather than - running it as a standalone binary. -* Fixes to the build process. +* Embed the CodeChat Editor Server inside the VSCode extension, rather than + running it as a standalone binary. +* Fixes to the build process. Version 0.1.34 -- 2025-Sep-08 ------------------------------ +-------------------------------------------------------------------------------- -* Make the Client text editable again. Improve handling of selections. -* In VSCode, load a new file in the Client after a short delay, rather than - immediately. This provides a more efficient process when changing files. +* Make the Client text editable again. Improve handling of selections. +* In VSCode, load a new file in the Client after a short delay, rather than + immediately. This provides a more efficient process when changing files. Version 0.1.33 -- 2025-Sep-02 ------------------------------ +-------------------------------------------------------------------------------- -* Change `` syntax, which now correctly translates from the - Client to source code, avoiding data loss in previous versions. -* Make the Client text editable from the IDE by fixing a bug introduced in - v0.1.32. -* Significantly reduce the size of the MathJax package. -* Correct a data loss bug when deleting multiple sequential doc blocks. +* Change `` syntax, which now correctly translates from the + Client to source code, avoiding data loss in previous versions. +* Make the Client text editable from the IDE by fixing a bug introduced in + v0.1.32. +* Significantly reduce the size of the MathJax package. +* Correct a data loss bug when deleting multiple sequential doc blocks. Version 0.1.32 -- 2025-Sep-01 ------------------------------ +-------------------------------------------------------------------------------- -* Improve tooling for VSCode extension. -* Improve selection handling in the Client. +* Improve tooling for VSCode extension. +* Improve selection handling in the Client. Version 0.1.31 -- 2025-Aug-31 ------------------------------ +-------------------------------------------------------------------------------- -* Correct EOL handling on Windows which caused data corruption. +* Correct EOL handling on Windows which caused data corruption. Version 0.1.30 -- 2025-Aug-30 ------------------------------ +-------------------------------------------------------------------------------- -* Correct ordering of messages sent to and from the Framework to the Client. -* Fix and improve test framework and error reporting. -* Improve MathJax bundling. -* Update PDF viewer. +* Correct ordering of messages sent to and from the Framework to the Client. +* Fix and improve test framework and error reporting. +* Improve MathJax bundling. +* Update PDF viewer. Version 0.1.29 -- 2025-Aug-23 ------------------------------ +-------------------------------------------------------------------------------- -* Fix bugs in PDF viewer. -* Fix error when scrolling before a document is loaded. -* Fix debug print statements. +* Fix bugs in PDF viewer. +* Fix error when scrolling before a document is loaded. +* Fix debug print statements. Version 0.1.28 -- 2025-Aug-13 ------------------------------ +-------------------------------------------------------------------------------- -* Fix navigation in document-only mode. -* Improve errors reporting in the VSCode extension. +* Fix navigation in document-only mode. +* Improve errors reporting in the VSCode extension. Version 0.1.27 -- 2025-Aug-05 ------------------------------ +-------------------------------------------------------------------------------- -* Correctly handle document-only files. -* Update to the MathJax 4.0.0 release. -* Improve release process -- include release notes taken from the changelog. +* Correctly handle document-only files. +* Update to the MathJax 4.0.0 release. +* Improve release process -- include release notes taken from the changelog. Earlier releases ----------------- - -* v0.1.26, 2025-Jul-31: - * Additional data corruption fixes when applying edits. - * Add more checks to detect data corruption. - * Update the file watcher to support the diff protocol. - * Send only changed fields when using the diff protocol. - * Provide basic synchronization between the IDE and Client. -* v0.1.25, 2025-Jul-29: - * Show notifications in Client when errors occur. - * For safety, close the Client if applying edits fails. -* v0.1.24, 2025-Jul-25:Β  - * Fix indexing in diffs for characters that use more than one UTF-16 code - unit, such as πŸ˜„,πŸ‘‰πŸΏ,πŸ‘¨β€πŸ‘¦, and πŸ‡ΊπŸ‡³. - * Fix data corruption with adjacent doc blocks. - * Translate line endings when loading a file from disk. -* v0.1.23, 2025-Jul-24 - * Correct diff errors in IDE with CRLF line endings. - * Upgrade to newest release of MathJax, TinyMCE. -* v0.1.22, 2025-Jul-24Β  - * Better support for opening a page in a web browser. - * Support HTTP basic authentication to restrict access; use - `codechat-editor-server serve --auth username:password`. - * Apply diffs to the IDE when available. - * Better handling for CRLF line endings. -* v0.1.21, 2025-Jul-18: - * Allow specifying the host address the server binds to. - * Send server logs to the console by default. -* v0.1.20, 2025-Jul-18: - * Correct data corruption in Client on delete/insert diff operations. -* v0.1.19, 2025-Jul-17:Β  - * Correctly apply diffs to Client document. - * Avoid deleting adjacent doc blocks. - * Correct error where edits in the IDE were ignored. - * Provide in-browser feedback on Mermaid errors. - * Fix word wrapping in Mermaid diagrams in lists. -* v0.1.18, 2025-Jul-14: - * Send diffs to the Client, which prevents visual jitter. -* v0.1.17, 2025-Apr-14: - * Fix heading level 3 and below word wrapping. - * Correctly handle unclosed fenced code blocks. -* v0.1.16, 2025-Apr-11: - * Fix to allow running inside a GitHub Codespace. - * Add: new command-line option to open a file/directory -- - `codechat-editor-server start [filename/diretory]`. -* v0.1.15, 2025-Mar-31: - * Correctly view binary files (images, PDFs, etc.) within a project. - * Include support for viewing PDF files in VSCode. -* v0.1.14, 2025-Mar-13: - * Correct translation of leading slash in Linux/OS X paths to/from a URL. - This fixes rewrites of URL in Markdown to long relative paths. -* v0.1.13, 2025-Mar-10: - * Show PDFs in the browser, instead of downloading them. - * Update to new release of Actix framework. -* v0.1.12, 2025-Mar-08: - * Fixed error when creating a new document in VSCode. - * Fixed error when updating a non-CodeChat Editor document in VSCode. -* v0.1.11, 2025-Feb-27: - * Fixed data corruption while editing math: typeset math, instead of LaTeX - source, was saved to the source file. Now, math is untypeset during - edits, then retypeset afterwards. - * Correctly handle webview shutdown in VSCode extension. -* v0.1.10, 2025-Feb-20: - * Update to the 2024 edition of Rust. - * Update dependencies. - * Update source formatting using current CodeChat Editor. -* v0.1.9, 2025-Jan-20: - * Correct word wrapping inside Mermaid diagrams. - * Correct translation after adding newlines to code blocks in the Editor. - * Use setext headings, different symbols for bullets. - * Drop prettier for word wrap. -* v0.1.8, 2025-Jan-12: - * Correctly handle file not found in VSCode. - * Correct filename handling on Windows. -* v0.1.7, 2025-Jan-08: - * Fixed hyperlink navigation. - * Fixed case-insensitive filename handling bugs. - * Improve filename handling in tests. -* v0.1.6, 2024-Dec-29: - * Improvements to the build tool. - * Corrections to the C parser. -* v0.1.5, 2024-Dec-21: - * Improvements to the build tool and tests. - * Fixed filewatcher bugs. -* v0.1.4, 2024-Dec-19: - * Added support for [Mermaid](https://mermaid.js.org/). - * Fixed MathJax packaging. - * Resize large images to fit in browser. - * Switch to new parser for Python and C/C++. - * Correct styles so that the selection and current line are visible. - * Created a build tool to automate the build process and added CI checks. - * Fixed OS-specific warnings and bugs. - * Fixed filewatcher bugs. -* v0.1.3, 2024-Nov-18: - * Switch to using MathJax 4 beta; load MathJax in the frame it's used, per - [MathJax issue #3309](https://github.com/mathjax/MathJax/issues/3309). - * Modernize graphviz-webcomponent build. - * Move CSS to `client/src`. -* v0.1.2, 2024-Nov-12: - * Fix [issue #28](https://github.com/bjones1/CodeChat_Editor/issues/28), - autosave failures. - * Fix filewatcher -- incorrect file path comparison. - * Fix errors saving mathematics in Markdown-only files. - * Improve spellchecking coverage. - * Fix data loss when the CodeChat Editor Client is not visible. -* v0.1.1, 2024-Nov-04: - * Added basic theme support; used a theme similar to Sphinx's Alabaster. - * Added support for printing. - * Added a user manual, improved documentation. - * Fixed a bug in the filewatcher that prevented saving changes made in the - Client. - * Added math support. - * Removed save button. - * Added support for Kotlin. - * Updates to the build system. - * Cross-platform fixes. -* v0.1.0, 2024-Oct-16: - * Initial release, with binaries for Windows only. Built with - manually-patched CodeMirror per [this - issue](https://github.com/bjones1/CodeChat_Editor/issues/27). \ No newline at end of file +-------------------------------------------------------------------------------- + +* v0.1.26, 2025-Jul-31: + * Additional data corruption fixes when applying edits. + * Add more checks to detect data corruption. + * Update the file watcher to support the diff protocol. + * Send only changed fields when using the diff protocol. + * Provide basic synchronization between the IDE and Client. +* v0.1.25, 2025-Jul-29: + * Show notifications in Client when errors occur. + * For safety, close the Client if applying edits fails. +* v0.1.24, 2025-Jul-25: + * Fix indexing in diffs for characters that use more than one UTF-16 code + unit, such as πŸ˜„,πŸ‘‰πŸΏ,πŸ‘¨β€πŸ‘¦, and πŸ‡ΊπŸ‡³. + * Fix data corruption with adjacent doc blocks. + * Translate line endings when loading a file from disk. +* v0.1.23, 2025-Jul-24 + * Correct diff errors in IDE with CRLF line endings. + * Upgrade to newest release of MathJax, TinyMCE. +* v0.1.22, 2025-Jul-24 + * Better support for opening a page in a web browser. + * Support HTTP basic authentication to restrict access; use + `codechat-editor-server serve --auth username:password`. + * Apply diffs to the IDE when available. + * Better handling for CRLF line endings. +* v0.1.21, 2025-Jul-18: + * Allow specifying the host address the server binds to. + * Send server logs to the console by default. +* v0.1.20, 2025-Jul-18: + * Correct data corruption in Client on delete/insert diff operations. +* v0.1.19, 2025-Jul-17: + * Correctly apply diffs to Client document. + * Avoid deleting adjacent doc blocks. + * Correct error where edits in the IDE were ignored. + * Provide in-browser feedback on Mermaid errors. + * Fix word wrapping in Mermaid diagrams in lists. +* v0.1.18, 2025-Jul-14: + * Send diffs to the Client, which prevents visual jitter. +* v0.1.17, 2025-Apr-14: + * Fix heading level 3 and below word wrapping. + * Correctly handle unclosed fenced code blocks. +* v0.1.16, 2025-Apr-11: + * Fix to allow running inside a GitHub Codespace. + * Add: new command-line option to open a file/directory -- + `codechat-editor-server start [filename/diretory]`. +* v0.1.15, 2025-Mar-31: + * Correctly view binary files (images, PDFs, etc.) within a project. + * Include support for viewing PDF files in VSCode. +* v0.1.14, 2025-Mar-13: + * Correct translation of leading slash in Linux/OS X paths to/from a URL. This + fixes rewrites of URL in Markdown to long relative paths. +* v0.1.13, 2025-Mar-10: + * Show PDFs in the browser, instead of downloading them. + * Update to new release of Actix framework. +* v0.1.12, 2025-Mar-08: + * Fixed error when creating a new document in VSCode. + * Fixed error when updating a non-CodeChat Editor document in VSCode. +* v0.1.11, 2025-Feb-27: + * Fixed data corruption while editing math: typeset math, instead of LaTeX + source, was saved to the source file. Now, math is untypeset during edits, + then retypeset afterwards. + * Correctly handle webview shutdown in VSCode extension. +* v0.1.10, 2025-Feb-20: + * Update to the 2024 edition of Rust. + * Update dependencies. + * Update source formatting using current CodeChat Editor. +* v0.1.9, 2025-Jan-20: + * Correct word wrapping inside Mermaid diagrams. + * Correct translation after adding newlines to code blocks in the Editor. + * Use setext headings, different symbols for bullets. + * Drop prettier for word wrap. +* v0.1.8, 2025-Jan-12: + * Correctly handle file not found in VSCode. + * Correct filename handling on Windows. +* v0.1.7, 2025-Jan-08: + * Fixed hyperlink navigation. + * Fixed case-insensitive filename handling bugs. + * Improve filename handling in tests. +* v0.1.6, 2024-Dec-29: + * Improvements to the build tool. + * Corrections to the C parser. +* v0.1.5, 2024-Dec-21: + * Improvements to the build tool and tests. + * Fixed filewatcher bugs. +* v0.1.4, 2024-Dec-19: + * Added support for [Mermaid](https://mermaid.js.org/). + * Fixed MathJax packaging. + * Resize large images to fit in browser. + * Switch to new parser for Python and C/C++. + * Correct styles so that the selection and current line are visible. + * Created a build tool to automate the build process and added CI checks. + * Fixed OS-specific warnings and bugs. + * Fixed filewatcher bugs. +* v0.1.3, 2024-Nov-18: + * Switch to using MathJax 4 beta; load MathJax in the frame it's used, per + [MathJax issue #3309](https://github.com/mathjax/MathJax/issues/3309). + * Modernize graphviz-webcomponent build. + * Move CSS to `client/src`. +* v0.1.2, 2024-Nov-12: + * Fix [issue #28](https://github.com/bjones1/CodeChat_Editor/issues/28), + autosave failures. + * Fix filewatcher -- incorrect file path comparison. + * Fix errors saving mathematics in Markdown-only files. + * Improve spellchecking coverage. + * Fix data loss when the CodeChat Editor Client is not visible. +* v0.1.1, 2024-Nov-04: + * Added basic theme support; used a theme similar to Sphinx's Alabaster. + * Added support for printing. + * Added a user manual, improved documentation. + * Fixed a bug in the filewatcher that prevented saving changes made in the + Client. + * Added math support. + * Removed save button. + * Added support for Kotlin. + * Updates to the build system. + * Cross-platform fixes. +* v0.1.0, 2024-Oct-16: + * Initial release, with binaries for Windows only. Built with manually-patched + CodeMirror per + [this issue](https://github.com/bjones1/CodeChat_Editor/issues/27). diff --git a/README.md b/README.md index 1f320278..c824ea31 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,9 @@ Likewise, the path to local images is relative to the current file's location | ----------------------------------- | --------------------------------- | | `![Monitor icon](docs/monitor.png)` | ![Monitor icon](docs/monitor.png) | -Although the CodeChat Editor allows drag-and-drop of images, the result is a -mess -- the image data is embedded directly in the source file. Avoid this; -instead, place images in a separate file, then reference them as shown above. +The CodeChat Editor disallows drag-and-drop of images, the result is a mess -- +the image data is embedded directly in the source file. Avoid this; instead, +place images in a separate file, then reference them as shown above. Projects -------------------------------------------------------------------------------- @@ -142,31 +142,31 @@ following characters should be escaped: `*`, `_`, `\`, `[`, `]`, `<`. Diagrams -------------------------------------------------------------------------------- -### Graphviz +### Mermaid The CodeChat Editor contains rudimentary support for diagrams created by -[Graphviz](https://graphviz.org/). For example, +[Mermaid](https://mermaid.js.org/). For example, -| Source | Rendered | -| ----------------------------------------------------- | ------------------------------------------------------ | -| `digraph { A -> B }` | digraph { A -> B } | +| Source | Rendered | +| --------------------------------------------- | ------------------------------------------- | +| `graph TD; A --> B;` | graph TD; A --> B; | To edit these diagrams, use an -[HTML entity encoder/decoder](https://mothereff.in/html-entities) and a Graphviz -editor such as [Edotor](https://edotor.net/). +[HTML entity encoder/decoder](https://mothereff.in/html-entities) and the +[Mermaid live editor](https://mermaid.live/). -### Mermaid +### Graphviz The CodeChat Editor contains rudimentary support for diagrams created by -[Mermaid](https://mermaid.js.org/). For example, +[Graphviz](https://graphviz.org/). For example, -| Source | Rendered | -| --------------------------------------------- | ---------------------------------------------- | -| `graph TD; A --> B;` | graph TD; A --> B; | +| Source | Rendered | +| ----------------------------------------------------- | --------------------------------------------------- | +| `digraph { A -> B }` | digraph { A -> B } | To edit these diagrams, use an -[HTML entity encoder/decoder](https://mothereff.in/html-entities) and the -[Mermaid live editor](https://mermaid.live/). +[HTML entity encoder/decoder](https://mothereff.in/html-entities) and a Graphviz +editor such as [Edotor](https://edotor.net/). ### PlantUML @@ -181,6 +181,15 @@ To edit these diagrams, paste the URL into the [PlantUML web server](https://www.plantuml.com/plantuml/uml), click Decode URL, edit, then copy and paste the SVG URL back to this file. +### Drawing programs + +Images files produced by drawing programs can be included, as long as they can +be saved in a web-compatible format (PNG, SVG, JPG, GIF, etc.). For example, the +draw.io editor embeds source data into the resulting image, so the image below +can be directly edited by that package: + +![](docs/sample_diagram.drawio.svg) + Supported languages -------------------------------------------------------------------------------- diff --git a/builder/Cargo.lock b/builder/Cargo.lock index 24c5ff75..7f60f02c 100644 --- a/builder/Cargo.lock +++ b/builder/Cargo.lock @@ -81,9 +81,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -234,15 +234,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -384,9 +384,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", diff --git a/builder/src/main.rs b/builder/src/main.rs index ec1a3719..0b7896d3 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -510,8 +510,6 @@ fn run_build() -> io::Result<()> { cargo build --manifest-path=$BUILDER_PATH/Cargo.toml; info "cargo build"; cargo build; - info "cargo test export_bindings"; - cargo test export_bindings; )?; // Clean out all bundled files before the rebuild. remove_dir_all_if_exists(format!("{CLIENT_PATH}/static/bundled"))?; @@ -528,7 +526,7 @@ fn run_client_build( // checks. skip_check_errors: bool, ) -> io::Result<()> { - // Ensure the JavaScript data structured generated from Rust are up to date. + // Ensure the JavaScript data structures generated from Rust are up to date. run_cmd!( info "cargo test export_bindings"; cargo test export_bindings; @@ -632,6 +630,12 @@ fn run_extensions_build( } run_script("npx", &napi_args, VSCODE_PATH, true)?; + // Ensure the JavaScript data structures generated from Rust are up to date. + run_cmd!( + info "cargo test export_bindings"; + cargo test export_bindings; + )?; + // The main build for the extension. run_script( &esbuild, diff --git a/client/package.json5 b/client/package.json5 index f94747cc..b6d22ae4 100644 --- a/client/package.json5 +++ b/client/package.json5 @@ -66,9 +66,9 @@ codemirror: '^6.0.2', 'graphviz-webcomponent': 'github:bjones1/graphviz-webcomponent#dist', mathjax: '4.0.0', - mermaid: '^11.12.1', + mermaid: '^11.12.2', 'npm-check-updates': '^19.1.2', - 'pdfjs-dist': '^5.4.394', + 'pdfjs-dist': '^5.4.449', tinymce: '^8.2.2', 'toastify-js': '^1.12.0', }, @@ -78,16 +78,16 @@ '@types/mocha': '^10.0.10', '@types/node': '^24.10.1', '@types/toastify-js': '^1.12.4', - '@typescript-eslint/eslint-plugin': '^8.46.4', - '@typescript-eslint/parser': '^8.46.4', + '@typescript-eslint/eslint-plugin': '^8.48.1', + '@typescript-eslint/parser': '^8.48.1', chai: '^6.2.1', - esbuild: '^0.27.0', + esbuild: '^0.27.1', eslint: '^9.39.1', 'eslint-config-prettier': '^10.1.8', 'eslint-plugin-import': '^2.32.0', 'eslint-plugin-prettier': '^5.5.4', mocha: '^11.7.5', - prettier: '^3.6.2', + prettier: '^3.7.4', typescript: '^5.9.3', }, scripts: { diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index ac137ef1..b54152e3 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -72,14 +72,14 @@ importers: specifier: 4.0.0 version: 4.0.0 mermaid: - specifier: ^11.12.1 - version: 11.12.1 + specifier: ^11.12.2 + version: 11.12.2 npm-check-updates: specifier: ^19.1.2 version: 19.1.2 pdfjs-dist: - specifier: ^5.4.394 - version: 5.4.394 + specifier: ^5.4.449 + version: 5.4.449 tinymce: specifier: ^8.2.2 version: 8.2.2 @@ -103,17 +103,17 @@ importers: specifier: ^1.12.4 version: 1.12.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.46.4 - version: 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.46.4 - version: 8.46.4(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) chai: specifier: ^6.2.1 version: 6.2.1 esbuild: - specifier: ^0.27.0 - version: 0.27.0 + specifier: ^0.27.1 + version: 0.27.1 eslint: specifier: ^9.39.1 version: 9.39.1 @@ -122,16 +122,16 @@ importers: version: 10.1.8(eslint@9.39.1) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) 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.6.2) + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.4) mocha: specifier: ^11.7.5 version: 11.7.5 prettier: - specifier: ^3.6.2 - version: 3.6.2 + specifier: ^3.7.4 + version: 3.7.4 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -141,9 +141,6 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/utils@9.3.0': - resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==} - '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -162,8 +159,8 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - '@codemirror/autocomplete@6.19.1': - resolution: {integrity: sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==} + '@codemirror/autocomplete@6.20.0': + resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} '@codemirror/commands@6.10.0': resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} @@ -225,158 +222,158 @@ packages: '@codemirror/view@6.38.8': resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + '@esbuild/aix-ppc64@0.27.1': + resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/android-arm64@0.27.1': + resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm@0.27.1': + resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-x64@0.27.1': + resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/darwin-arm64@0.27.1': + resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-x64@0.27.1': + resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/freebsd-arm64@0.27.1': + resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-x64@0.27.1': + resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/linux-arm64@0.27.1': + resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm@0.27.1': + resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-ia32@0.27.1': + resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-loong64@0.27.1': + resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-mips64el@0.27.1': + resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-ppc64@0.27.1': + resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + '@esbuild/linux-riscv64@0.27.1': + resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + '@esbuild/linux-s390x@0.27.1': + resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + '@esbuild/linux-x64@0.27.1': + resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + '@esbuild/netbsd-arm64@0.27.1': + resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + '@esbuild/netbsd-x64@0.27.1': + resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + '@esbuild/openbsd-arm64@0.27.1': + resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + '@esbuild/openbsd-x64@0.27.1': + resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + '@esbuild/openharmony-arm64@0.27.1': + resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + '@esbuild/sunos-x64@0.27.1': + resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + '@esbuild/win32-arm64@0.27.1': + resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + '@esbuild/win32-ia32@0.27.1': + resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + '@esbuild/win32-x64@0.27.1': + resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -403,8 +400,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.1': @@ -438,15 +435,15 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@3.0.2': - resolution: {integrity: sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==} + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@lezer/common@1.3.0': - resolution: {integrity: sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==} + '@lezer/common@1.4.0': + resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} '@lezer/cpp@1.1.3': resolution: {integrity: sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==} @@ -472,8 +469,8 @@ packages: '@lezer/json@1.0.3': resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} - '@lezer/lr@1.4.3': - resolution: {integrity: sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==} + '@lezer/lr@1.4.4': + resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} '@lezer/markdown@1.6.0': resolution: {integrity: sha512-AXb98u3M6BEzTnreBnGtQaF7xFTiMA92Dsy5tqEjpacbjRxDSFdN4bKJo9uvU4cEEOS7D2B9MT7kvDgOEIzJSw==} @@ -502,82 +499,70 @@ packages: '@mermaid-js/parser@0.6.3': resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} - '@napi-rs/canvas-android-arm64@0.1.82': - resolution: {integrity: sha512-bvZhN0iI54ouaQOrgJV96H2q7J3ZoufnHf4E1fUaERwW29Rz4rgicohnAg4venwBJZYjGl5Yl3CGmlAl1LZowQ==} + '@napi-rs/canvas-android-arm64@0.1.83': + resolution: {integrity: sha512-TbKM2fh9zXjqFIU8bgMfzG7rkrIYdLKMafgPhFoPwKrpWk1glGbWP7LEu8Y/WrMDqTGFdRqUmuX89yQEzZbkiw==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.82': - resolution: {integrity: sha512-InuBHKCyuFqhNwNr4gpqazo5Xp6ltKflqOLiROn4hqAS8u21xAHyYCJRgHwd+a5NKmutFTaRWeUIT/vxWbU/iw==} + '@napi-rs/canvas-darwin-arm64@0.1.83': + resolution: {integrity: sha512-gp8IDVUloPUmkepHly4xRUOfUJSFNvA4jR7ZRF5nk3YcGzegSFGeICiT4PnYyPgSKEhYAFe1Y2XNy0Mp6Tu8mQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.82': - resolution: {integrity: sha512-aQGV5Ynn96onSXcuvYb2y7TRXD/t4CL2EGmnGqvLyeJX1JLSNisKQlWN/1bPDDXymZYSdUqbXehj5qzBlOx+RQ==} + '@napi-rs/canvas-darwin-x64@0.1.83': + resolution: {integrity: sha512-r4ZJxiP9OgUbdGZhPDEXD3hQ0aIPcVaywtcTXvamYxTU/SWKAbKVhFNTtpRe1J30oQ25gWyxTkUKSBgUkNzdnw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.82': - resolution: {integrity: sha512-YIUpmHWeHGGRhWitT1KJkgj/JPXPfc9ox8oUoyaGPxolLGPp5AxJkq8wIg8CdFGtutget968dtwmx71m8o3h5g==} + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.83': + resolution: {integrity: sha512-Uc6aSB05qH1r+9GUDxIE6F5ZF7L0nTFyyzq8ublWUZhw8fEGK8iy931ff1ByGFT04+xHJad1kBcL4R1ZEV8z7Q==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.82': - resolution: {integrity: sha512-AwLzwLBgmvk7kWeUgItOUor/QyG31xqtD26w1tLpf4yE0hiXTGp23yc669aawjB6FzgIkjh1NKaNS52B7/qEBQ==} + '@napi-rs/canvas-linux-arm64-gnu@0.1.83': + resolution: {integrity: sha512-eEeaJA7V5KOFq7W0GtoRVbd3ak8UZpK+XLkCgUiFGtlunNw+ZZW9Cr/92MXflGe7o3SqqMUg+f975LPxO/vsOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-arm64-musl@0.1.82': - resolution: {integrity: sha512-moZWuqepAwWBffdF4JDadt8TgBD02iMhG6I1FHZf8xO20AsIp9rB+p0B8Zma2h2vAF/YMjeFCDmW5un6+zZz9g==} + '@napi-rs/canvas-linux-arm64-musl@0.1.83': + resolution: {integrity: sha512-cAvonp5XpbatVGegF9lMQNchs3z5RH6EtamRVnQvtoRtwbzOMcdzwuLBqDBQxQF79MFbuZNkWj3YRJjZCjHVzw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.82': - resolution: {integrity: sha512-w9++2df2kG9eC9LWYIHIlMLuhIrKGQYfUxs97CwgxYjITeFakIRazI9LYWgVzEc98QZ9x9GQvlicFsrROV59MQ==} + '@napi-rs/canvas-linux-riscv64-gnu@0.1.83': + resolution: {integrity: sha512-WFUPQ9qZy31vmLxIJ3MfmHw+R2g/mLCgk8zmh7maJW8snV3vLPA7pZfIS65Dc61EVDp1vaBskwQ2RqPPzwkaew==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@napi-rs/canvas-linux-x64-gnu@0.1.82': - resolution: {integrity: sha512-lZulOPwrRi6hEg/17CaqdwWEUfOlIJuhXxincx1aVzsVOCmyHf+xFq4i6liJl1P+x2v6Iz2Z/H5zHvXJCC7Bwg==} + '@napi-rs/canvas-linux-x64-gnu@0.1.83': + resolution: {integrity: sha512-X9YwIjsuy50WwOyYeNhEHjKHO8rrfH9M4U8vNqLuGmqsZdKua/GrUhdQGdjq7lTgdY3g4+Ta5jF8MzAa7UAs/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-linux-x64-musl@0.1.82': - resolution: {integrity: sha512-Be9Wf5RTv1w6GXlTph55K3PH3vsAh1Ax4T1FQY1UYM0QfD0yrwGdnJ8/fhqw7dEgMjd59zIbjJQC8C3msbGn5g==} + '@napi-rs/canvas-linux-x64-musl@0.1.83': + resolution: {integrity: sha512-Vv2pLWQS8EnlSM1bstJ7vVhKA+mL4+my4sKUIn/bgIxB5O90dqiDhQjUDLP+5xn9ZMestRWDt3tdQEkGAmzq/A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-win32-x64-msvc@0.1.82': - resolution: {integrity: sha512-LN/i8VrvxTDmEEK1c10z2cdOTkWT76LlTGtyZe5Kr1sqoSomKeExAjbilnu1+oee5lZUgS5yfZ2LNlVhCeARuw==} + '@napi-rs/canvas-win32-x64-msvc@0.1.83': + resolution: {integrity: sha512-K1TtjbScfRNYhq8dengLLufXGbtEtWdUXPV505uLFPovyGHzDUGXLFP/zUJzj6xWXwgUjHNLgEPIt7mye0zr6Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.82': - resolution: {integrity: sha512-FGjyUBoF0sl1EenSiE4UV2WYu76q6F9GSYedq5EiOCOyGYoQ/Owulcv6rd7v/tWOpljDDtefXXIaOCJrVKem4w==} + '@napi-rs/canvas@0.1.83': + resolution: {integrity: sha512-f9GVB9VNc9vn/nroc9epXRNkVpvNPZh69+qzLJIm9DfruxFqX0/jsXG46OGWAJgkO4mN0HvFHjRROMXKVmPszg==} engines: {node: '>= 10'} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -715,63 +700,63 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.46.4': - resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==} + '@typescript-eslint/eslint-plugin@8.48.1': + resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.46.4 + '@typescript-eslint/parser': ^8.48.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.46.4': - resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==} + '@typescript-eslint/parser@8.48.1': + resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} 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.46.4': - resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==} + '@typescript-eslint/project-service@8.48.1': + resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.46.4': - resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==} + '@typescript-eslint/scope-manager@8.48.1': + resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.46.4': - resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==} + '@typescript-eslint/tsconfig-utils@8.48.1': + resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.46.4': - resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==} + '@typescript-eslint/type-utils@8.48.1': + resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} 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.46.4': - resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==} + '@typescript-eslint/types@8.48.1': + resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.46.4': - resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==} + '@typescript-eslint/typescript-estree@8.48.1': + resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.46.4': - resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==} + '@typescript-eslint/utils@8.48.1': + resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} 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.46.4': - resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==} + '@typescript-eslint/visitor-keys@8.48.1': + resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -851,10 +836,6 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} @@ -926,9 +907,6 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -1200,8 +1178,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + esbuild@0.27.1: + resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} hasBin: true @@ -1309,36 +1287,31 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - 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'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1392,26 +1365,18 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} - engines: {node: '>=18'} - globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1557,10 +1522,6 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -1645,9 +1606,6 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - langium@3.3.1: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} @@ -1662,10 +1620,6 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1695,16 +1649,8 @@ packages: mathjax@4.0.0: resolution: {integrity: sha512-ThMPHiPl9ibZBInAmfoTCNq9MgCdH7ChIQ9YhKFc325noJ4DMzy9/Q14qdcuPzVJjEmC3kyXhwnERZWX3hbWzQ==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - mermaid@11.12.1: - resolution: {integrity: sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1782,8 +1728,8 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.5.0: - resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -1810,23 +1756,20 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pdfjs-dist@5.4.394: - resolution: {integrity: sha512-9ariAYGqUJzx+V/1W4jHyiyCep6IZALmDzoaTLZ6VNu8q9LWi1/ukhzHgE2Xsx96AZi0mbZuK4/ttIbqSbLypg==} + pdfjs-dist@5.4.449: + resolution: {integrity: sha512-CegnUaT0QwAyQMS+7o2POr4wWUNNe8VaKKlcuoRHeYo98cVnqPpwOXNSx6Trl6szH02JrRcsPgletV6GmF3LtQ==} engines: {node: '>=20.16.0 || >=22.3.0'} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -1845,8 +1788,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true @@ -1854,12 +1797,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -1888,19 +1825,12 @@ packages: engines: {node: '>= 0.4'} hasBin: true - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} @@ -2040,13 +1970,13 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinymce@8.2.2: resolution: {integrity: sha512-CFDSZwciMvFGW2czK/Xig1HcOGpXI0qcQMIqaIcG2F4RuuTdf+LQTreyEZunAJoFTQ9L0KAugOqL7OA5TJkoAA==} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - toastify-js@1.12.0: resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} @@ -2188,11 +2118,9 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 1.5.0 + package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@antfu/utils@9.3.0': {} - '@braintree/sanitize-url@7.1.1': {} '@chevrotain/cst-dts-gen@11.0.3': @@ -2212,19 +2140,19 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@codemirror/autocomplete@6.19.1': + '@codemirror/autocomplete@6.20.0': dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@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 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@codemirror/lang-cpp@6.0.3': dependencies: @@ -2233,29 +2161,29 @@ snapshots: '@codemirror/lang-css@6.3.1': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@codemirror/lang-go@6.0.1': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/go': 1.0.1 '@codemirror/lang-html@6.4.11': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/lang-css': 6.3.1 '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@lezer/html': 1.3.12 @@ -2266,12 +2194,12 @@ snapshots: '@codemirror/lang-javascript@6.2.4': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/javascript': 1.5.4 '@codemirror/lang-json@6.0.2': @@ -2281,12 +2209,12 @@ snapshots: '@codemirror/lang-markdown@6.5.0': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/markdown': 1.6.0 '@codemirror/lang-php@6.0.2': @@ -2294,15 +2222,15 @@ snapshots: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/php': 1.0.5 '@codemirror/lang-python@6.2.1': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/python': 1.1.18 '@codemirror/lang-rust@6.0.2': @@ -2312,39 +2240,39 @@ snapshots: '@codemirror/lang-sql@6.10.0': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-xml@6.1.0': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/xml': 1.0.6 '@codemirror/lang-yaml@6.1.2': dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/yaml': 1.0.3 '@codemirror/language@6.11.3': dependencies: '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 style-mod: 4.1.3 '@codemirror/lint@6.9.2': @@ -2370,82 +2298,82 @@ snapshots: style-mod: 4.1.3 w3c-keyname: 2.2.8 - '@esbuild/aix-ppc64@0.27.0': + '@esbuild/aix-ppc64@0.27.1': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/android-arm64@0.27.1': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/android-arm@0.27.1': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/android-x64@0.27.1': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/darwin-arm64@0.27.1': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/darwin-x64@0.27.1': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/freebsd-arm64@0.27.1': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/freebsd-x64@0.27.1': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/linux-arm64@0.27.1': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/linux-arm@0.27.1': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/linux-ia32@0.27.1': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/linux-loong64@0.27.1': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/linux-mips64el@0.27.1': optional: true - '@esbuild/linux-ppc64@0.27.0': + '@esbuild/linux-ppc64@0.27.1': optional: true - '@esbuild/linux-riscv64@0.27.0': + '@esbuild/linux-riscv64@0.27.1': optional: true - '@esbuild/linux-s390x@0.27.0': + '@esbuild/linux-s390x@0.27.1': optional: true - '@esbuild/linux-x64@0.27.0': + '@esbuild/linux-x64@0.27.1': optional: true - '@esbuild/netbsd-arm64@0.27.0': + '@esbuild/netbsd-arm64@0.27.1': optional: true - '@esbuild/netbsd-x64@0.27.0': + '@esbuild/netbsd-x64@0.27.1': optional: true - '@esbuild/openbsd-arm64@0.27.0': + '@esbuild/openbsd-arm64@0.27.1': optional: true - '@esbuild/openbsd-x64@0.27.0': + '@esbuild/openbsd-x64@0.27.1': optional: true - '@esbuild/openharmony-arm64@0.27.0': + '@esbuild/openharmony-arm64@0.27.1': optional: true - '@esbuild/sunos-x64@0.27.0': + '@esbuild/sunos-x64@0.27.1': optional: true - '@esbuild/win32-arm64@0.27.0': + '@esbuild/win32-arm64@0.27.1': optional: true - '@esbuild/win32-ia32@0.27.0': + '@esbuild/win32-ia32@0.27.1': optional: true - '@esbuild/win32-x64@0.27.0': + '@esbuild/win32-x64@0.27.1': optional: true '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': @@ -2471,7 +2399,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 debug: 4.4.3(supports-color@8.1.1) @@ -2507,18 +2435,11 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@3.0.2': + '@iconify/utils@3.1.0': dependencies: '@antfu/install-pkg': 1.1.0 - '@antfu/utils': 9.3.0 '@iconify/types': 2.0.0 - debug: 4.4.3(supports-color@8.1.1) - globals: 15.15.0 - kolorist: 1.8.0 - local-pkg: 1.1.2 mlly: 1.8.0 - transitivePeerDependencies: - - supports-color '@isaacs/cliui@8.0.2': dependencies: @@ -2529,92 +2450,92 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@lezer/common@1.3.0': {} + '@lezer/common@1.4.0': {} '@lezer/cpp@1.1.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/css@1.3.0': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/go@1.0.1': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/highlight@1.2.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/html@1.3.12': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/java@1.1.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/javascript@1.5.4': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/json@1.0.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 - '@lezer/lr@1.4.3': + '@lezer/lr@1.4.4': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/markdown@1.6.0': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 '@lezer/php@1.0.5': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/python@1.1.18': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/rust@1.0.2': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/xml@1.0.6': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/yaml@1.0.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@marijn/find-cluster-break@1.0.2': {} @@ -2624,62 +2545,50 @@ snapshots: dependencies: langium: 3.3.1 - '@napi-rs/canvas-android-arm64@0.1.82': + '@napi-rs/canvas-android-arm64@0.1.83': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.82': + '@napi-rs/canvas-darwin-arm64@0.1.83': optional: true - '@napi-rs/canvas-darwin-x64@0.1.82': + '@napi-rs/canvas-darwin-x64@0.1.83': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.82': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.83': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.82': + '@napi-rs/canvas-linux-arm64-gnu@0.1.83': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.82': + '@napi-rs/canvas-linux-arm64-musl@0.1.83': optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.82': + '@napi-rs/canvas-linux-riscv64-gnu@0.1.83': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.82': + '@napi-rs/canvas-linux-x64-gnu@0.1.83': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.82': + '@napi-rs/canvas-linux-x64-musl@0.1.83': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.82': + '@napi-rs/canvas-win32-x64-msvc@0.1.83': optional: true - '@napi-rs/canvas@0.1.82': + '@napi-rs/canvas@0.1.83': optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.82 - '@napi-rs/canvas-darwin-arm64': 0.1.82 - '@napi-rs/canvas-darwin-x64': 0.1.82 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.82 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.82 - '@napi-rs/canvas-linux-arm64-musl': 0.1.82 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.82 - '@napi-rs/canvas-linux-x64-gnu': 0.1.82 - '@napi-rs/canvas-linux-x64-musl': 0.1.82 - '@napi-rs/canvas-win32-x64-msvc': 0.1.82 + '@napi-rs/canvas-android-arm64': 0.1.83 + '@napi-rs/canvas-darwin-arm64': 0.1.83 + '@napi-rs/canvas-darwin-x64': 0.1.83 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.83 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.83 + '@napi-rs/canvas-linux-arm64-musl': 0.1.83 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.83 + '@napi-rs/canvas-linux-x64-gnu': 0.1.83 + '@napi-rs/canvas-linux-x64-musl': 0.1.83 + '@napi-rs/canvas-win32-x64-msvc': 0.1.83 optional: true - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - '@pkgjs/parseargs@0.11.0': optional: true @@ -2832,14 +2741,14 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 eslint: 9.39.1 graphemer: 1.4.0 ignore: 7.0.5 @@ -2849,41 +2758,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.46.4(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) - '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.46.4': + '@typescript-eslint/scope-manager@8.48.1': dependencies: - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 - '@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -2891,38 +2800,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.46.4': {} + '@typescript-eslint/types@8.48.1': {} - '@typescript-eslint/typescript-estree@8.46.4(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.46.4(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3(supports-color@8.1.1) - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.46.4': + '@typescript-eslint/visitor-keys@8.48.1': dependencies: - '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/types': 8.48.1 eslint-visitor-keys: 4.2.1 acorn-jsx@5.3.2(acorn@8.15.0): @@ -3019,10 +2927,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - browser-stdout@1.3.1: {} call-bind-apply-helpers@1.0.2: @@ -3079,7 +2983,7 @@ snapshots: codemirror@6.0.2: dependencies: - '@codemirror/autocomplete': 6.19.1 + '@codemirror/autocomplete': 6.20.0 '@codemirror/commands': 6.10.0 '@codemirror/language': 6.11.3 '@codemirror/lint': 6.9.2 @@ -3101,8 +3005,6 @@ snapshots: confbox@0.1.8: {} - confbox@0.2.2: {} - cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -3457,34 +3359,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.0: + esbuild@0.27.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 + '@esbuild/aix-ppc64': 0.27.1 + '@esbuild/android-arm': 0.27.1 + '@esbuild/android-arm64': 0.27.1 + '@esbuild/android-x64': 0.27.1 + '@esbuild/darwin-arm64': 0.27.1 + '@esbuild/darwin-x64': 0.27.1 + '@esbuild/freebsd-arm64': 0.27.1 + '@esbuild/freebsd-x64': 0.27.1 + '@esbuild/linux-arm': 0.27.1 + '@esbuild/linux-arm64': 0.27.1 + '@esbuild/linux-ia32': 0.27.1 + '@esbuild/linux-loong64': 0.27.1 + '@esbuild/linux-mips64el': 0.27.1 + '@esbuild/linux-ppc64': 0.27.1 + '@esbuild/linux-riscv64': 0.27.1 + '@esbuild/linux-s390x': 0.27.1 + '@esbuild/linux-x64': 0.27.1 + '@esbuild/netbsd-arm64': 0.27.1 + '@esbuild/netbsd-x64': 0.27.1 + '@esbuild/openbsd-arm64': 0.27.1 + '@esbuild/openbsd-x64': 0.27.1 + '@esbuild/openharmony-arm64': 0.27.1 + '@esbuild/sunos-x64': 0.27.1 + '@esbuild/win32-arm64': 0.27.1 + '@esbuild/win32-ia32': 0.27.1 + '@esbuild/win32-x64': 0.27.1 escalade@3.2.0: {} @@ -3502,17 +3404,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(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.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3523,7 +3425,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(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.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -3535,16 +3437,16 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(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.6.2): + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.4): dependencies: eslint: 9.39.1 - prettier: 3.6.2 + prettier: 3.7.4 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: @@ -3566,7 +3468,7 @@ snapshots: '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 @@ -3616,36 +3518,22 @@ snapshots: esutils@2.0.3: {} - exsolve@1.0.8: {} - fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fastq@1.19.1: - dependencies: - reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3710,15 +3598,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@10.5.0: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 @@ -3729,8 +3613,6 @@ snapshots: globals@14.0.0: {} - globals@15.15.0: {} - globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -3862,8 +3744,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-number@7.0.0: {} - is-path-inside@3.0.3: {} is-plain-obj@2.1.0: {} @@ -3943,8 +3823,6 @@ snapshots: khroma@2.1.0: {} - kolorist@1.8.0: {} - langium@3.3.1: dependencies: chevrotain: 11.0.3 @@ -3962,12 +3840,6 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - local-pkg@1.1.2: - dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 - quansync: 0.2.11 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3991,12 +3863,10 @@ snapshots: dependencies: '@mathjax/mathjax-newcm-font': 4.0.0 - merge2@1.4.1: {} - - mermaid@11.12.1: + mermaid@11.12.2: dependencies: '@braintree/sanitize-url': 7.1.1 - '@iconify/utils': 3.0.2 + '@iconify/utils': 3.1.0 '@mermaid-js/parser': 0.6.3 '@types/d3': 7.4.3 cytoscape: 3.33.1 @@ -4015,13 +3885,6 @@ snapshots: stylis: 4.3.6 ts-dedent: 2.2.0 uuid: 11.1.0 - transitivePeerDependencies: - - supports-color - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 minimatch@3.1.2: dependencies: @@ -4050,7 +3913,7 @@ snapshots: diff: 7.0.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 - glob: 10.4.5 + glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 js-yaml: 4.1.1 @@ -4130,7 +3993,7 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@1.5.0: {} + package-manager-detector@1.6.0: {} parent-module@1.0.1: dependencies: @@ -4151,13 +4014,13 @@ snapshots: pathe@2.0.3: {} - pdfjs-dist@5.4.394: + pdfjs-dist@5.4.449: optionalDependencies: - '@napi-rs/canvas': 0.1.82 + '@napi-rs/canvas': 0.1.83 picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@4.0.3: {} pkg-types@1.3.1: dependencies: @@ -4165,12 +4028,6 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 - pkg-types@2.3.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.8 - pathe: 2.0.3 - points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -4186,14 +4043,10 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.6.2: {} + prettier@3.7.4: {} punycode@2.3.1: {} - quansync@0.2.11: {} - - queue-microtask@1.2.3: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -4230,8 +4083,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.1.0: {} - robust-predicates@3.0.2: {} roughjs@4.6.6: @@ -4241,10 +4092,6 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - rw@1.3.3: {} safe-array-concat@1.1.3: @@ -4408,11 +4255,12 @@ snapshots: tinyexec@1.0.2: {} - tinymce@8.2.2: {} - - to-regex-range@5.0.1: + tinyglobby@0.2.15: dependencies: - is-number: 7.0.0 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinymce@8.2.2: {} toastify-js@1.12.0: {} diff --git a/client/src/CodeChatEditor.mts b/client/src/CodeChatEditor.mts index 7a271607..4ba9d95c 100644 --- a/client/src/CodeChatEditor.mts +++ b/client/src/CodeChatEditor.mts @@ -66,10 +66,11 @@ import "graphviz-webcomponent"; import { tinymce, init, Editor } from "./tinymce-config.mjs"; import { CodeChatForWeb, - CodeMirrorDocBlockTuple, CodeMirrorDiffable, UpdateMessageContents, CodeMirror, + autosave_timeout_ms, + rand, } from "./shared_types.mjs"; import { show_toast } from "./show_toast.mjs"; @@ -348,6 +349,7 @@ const save_lp = (is_dirty: boolean) => { update.contents = { metadata: current_metadata, source: code_mirror_diffable, + version: rand(), }; } @@ -368,10 +370,14 @@ const on_save = async (only_if_dirty: boolean = false) => { if (only_if_dirty && !is_dirty) { return; } + clearAutosaveTimer(); + // Save the provided contents back to the filesystem, by // sending an update message over the websocket. const webSocketComm = parent.window.CodeChatEditorFramework.webSocketComm; - console_log("CodeChat Editor Client: sent Update - saving document."); + console_log( + "CodeChat Editor Client: sent Update - saving document/updating cursor location.", + ); await new Promise(async (resolve) => { webSocketComm.send_message({ Update: save_lp(is_dirty) }, () => resolve(0), @@ -395,7 +401,7 @@ export const startAutosaveTimer = () => { autosaveTimeoutId = window.setTimeout(() => { console_log("CodeChat Editor Client: autosaving."); on_save(); - }, 1000); + }, autosave_timeout_ms); }; const clearAutosaveTimer = () => { diff --git a/client/src/CodeChatEditorFramework.mts b/client/src/CodeChatEditorFramework.mts index 86b46c6e..43f26b8e 100644 --- a/client/src/CodeChatEditorFramework.mts +++ b/client/src/CodeChatEditorFramework.mts @@ -45,6 +45,7 @@ import { on_error, on_dom_content_loaded, } from "./CodeChatEditor.mjs"; +import { ResultErrTypes } from "./rust-types/ResultErrTypes.js"; // Websocket // ----------------------------------------------------------------------------- @@ -80,6 +81,10 @@ class WebSocketComm { // IDE and passed back to it, but not otherwise used by the Framework. current_filename: string | undefined = undefined; + // The version number of the current file. This default value will be overwritten when + // the first `Update` is sent. + version = 0.0; + // True when the iframe is loading, so that an `Update` should be postponed // until the page load is finished. Otherwise, the page is fully loaded, so // the `Update` may be applied immediately. @@ -148,12 +153,31 @@ class WebSocketComm { ) { const msg = `Ignoring update for ${current_update.file_path} because it's not the current file ${this.current_filename}.`; report_error(msg); - this.send_result(id, msg); + this.send_result(id, { + IgnoredUpdate: [ + current_update.file_path, + this.current_filename, + ], + }); return; } const contents = current_update.contents; const cursor_position = current_update.cursor_position; if (contents !== undefined) { + // Check and update the version. If this is a diff, ensure the diff was made against the version of the file we have. + if ("Diff" in contents.source) { + if ( + contents.source.Diff.version !== + this.version + ) { + report_error( + `Out of sync: Client version ${this.version} !== incoming version ${contents.source.Diff.version}.`, + ); + this.send_result(id, "OutOfSync"); + return; + } + } + this.version = contents.version; // I'd prefer to use a system-maintained value to // determine the ready state of the iframe, such as // [readyState](https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState). @@ -199,7 +223,7 @@ class WebSocketComm { ); } - this.send_result(id, null); + this.send_result(id); }); break; @@ -234,7 +258,7 @@ class WebSocketComm { // `current_filename` should be set on the next `Update` // message. this.current_filename = undefined; - this.send_result(id, null); + this.send_result(id); }); break; @@ -264,7 +288,11 @@ class WebSocketComm { value, )})`; report_error(msg); - this.send_result(id, msg); + this.send_result(id, { + ClientIllegalMessageReceived: `${key}(${format_struct( + value, + )})`, + }); break; } }; @@ -303,6 +331,8 @@ class WebSocketComm { if (typeof message == "object" && "Update" in message) { assert(this.current_filename !== undefined); message.Update.file_path = this.current_filename!; + // Update the version of this file if it's provided. + this.version = message.Update.contents?.version ?? this.version; } console_log( `CodeChat Editor Client: sent message ${id}, ${format_struct(message)}`, @@ -348,9 +378,9 @@ class WebSocketComm { // Send a result (a response to a message from the server) back to the // server. - send_result = (id: number, result: string | null = null) => { + send_result = (id: number, result?: ResultErrTypes) => { const message: EditorMessageContents = { - Result: result === null ? { Ok: "Void" } : { Err: result }, + Result: result === undefined ? { Ok: "Void" } : { Err: result }, }; console_log( `CodeChat Client: sending result id = ${id}, message = ${format_struct(message)}`, diff --git a/client/src/CodeMirror-integration.mts b/client/src/CodeMirror-integration.mts index 60818d63..a7e2839f 100644 --- a/client/src/CodeMirror-integration.mts +++ b/client/src/CodeMirror-integration.mts @@ -101,14 +101,8 @@ import { show_toast } from "./show_toast.mjs"; // ----------------------------------------------------------------------------- let current_view: EditorView; let tinymce_singleton: Editor | undefined; -// When true, don't update on the next call to `on_dirty`. See that function for -// more info. -let ignore_next_dirty = false; // This indicates that a call to `on_dirty` is scheduled, but hasn't run yet. let on_dirty_scheduled = false; -// True to ignore the next text selection change, since updates to the cursor or -// scroll position from the Client trigged this change. -let ignore_selection_change = false; // Options used when creating a `Decoration`. const decorationOptions = { @@ -122,8 +116,12 @@ declare global { } } +// When this is included in a transaction, don't update from/to of doc blocks. const docBlockFreezeAnnotation = Annotation.define(); +// When this is included in a transaction, don't send autosave scroll/cursor location updates. +const noAutosaveAnnotation = Annotation.define(); + // Doc blocks in CodeMirror // ----------------------------------------------------------------------------- // @@ -191,6 +189,7 @@ export const docBlockField = StateField.define({ effect.value.indent, effect.value.delimiter, effect.value.content, + false, ), ...decorationOptions, }).range(effect.value.from, effect.value.to), @@ -277,6 +276,8 @@ export const docBlockField = StateField.define({ prev.spec.widget.contents, effect.value.contents, ), + // Assume this isn't a user change unless it's specified. + effect.value.is_user_change ?? false, ), ...decorationOptions, }).range(from, to), @@ -327,7 +328,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), ), @@ -367,6 +373,8 @@ 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. @@ -409,6 +417,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. @@ -417,9 +426,9 @@ class DocBlockWidget extends WidgetType { eq(other: DocBlockWidget) { return ( - other.indent == this.indent && - other.delimiter == this.delimiter && - other.contents == this.contents + other.indent === this.indent && + other.delimiter === this.delimiter && + other.contents === this.contents ); } @@ -447,12 +456,10 @@ class DocBlockWidget extends WidgetType { // [docs](https://codemirror.net/docs/ref/#view.WidgetType.updateDOM), // "Update a DOM element created by a widget of the same type (but // different, non-eq content) to reflect this widget." - updateDOM(dom: HTMLElement, view: EditorView): boolean { - // See if this update was produced by a change in TinyMCE text, which - // means the DOM is already updated. - if ((dom as any).update_complete === true) { - // Yes, so clear this update flag before returning. - delete (dom as any).update_complete; + 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) { + console.log("user change -- skipping DOM update."); return true; } (dom.childNodes[0] as HTMLDivElement).innerHTML = this.indent; @@ -462,9 +469,14 @@ class DocBlockWidget extends WidgetType { const [contents_div, is_tinymce] = get_contents(dom); window.MathJax.typesetClear(contents_div); if (is_tinymce) { - ignore_next_dirty = true; + // Save the cursor location before the update, then restore it afterwards, if TinyMCE has focus. + const sel = tinymce_singleton!.hasFocus() + ? saveSelection() + : undefined; tinymce_singleton!.setContent(this.contents); - tinymce_singleton!.save(); + if (sel !== undefined) { + restoreSelection(sel); + } } else { contents_div.innerHTML = this.contents; } @@ -500,6 +512,97 @@ 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", + ); + 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!; + 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 + ); + // 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 ( @@ -559,26 +662,23 @@ const element_is_in_doc_block = ( // untypeset, then the dirty ignored. // 3. When MathJax typesets math on a TinyMCE focus out event, the dirty flag // gets set. This should be ignored. However, typesetting is an async -// operation, so we assume it's OK to await the typeset completion, then -// clear the `ignore_next_dirty flag`. This will lead to nasty bugs at some -// point. +// operation, so we assume it's OK to await the typeset completion. +// This will lead to nasty bugs at some point. // 4. When an HTML doc block is assigned to the TinyMCE instance for editing, // the dirty flag is set. This must be ignored. const on_dirty = ( // The div that's dirty. It must be a child of the doc block div. event_target: HTMLElement, ) => { - if (ignore_next_dirty) { - ignore_next_dirty = false; - return; - } - if (on_dirty_scheduled) { return; } + on_dirty_scheduled = true; // Only run this after typesetting is done. - window.MathJax.whenReady(() => { + window.MathJax.whenReady(async () => { + console.log("Starting update for user change."); + on_dirty_scheduled = false; // Find the doc block parent div. const target = (event_target as HTMLDivElement).closest( ".CodeChat-doc", @@ -603,25 +703,17 @@ const on_dirty = ( const contents = is_tinymce ? tinymce_singleton!.save() : contents_div.innerHTML; - // Although this is async, nothing following this call depends on its completion. - mathJaxTypeset(contents_div); - // Sorta ugly hack: TinyMCE stores its data in the DOM. CodeMirror stores - // state in external structs. We need to update the CodeMirror state, but - // not overwrite the DOM with this "new" state, since the DOM is already - // updated. So, signal that this "update" is already done. - (target as any).update_complete = true; - let effects: StateEffect[] = [ - updateDocBlock.of({ - from, - indent, - delimiter, - contents, - }), - ]; - - current_view.dispatch({ effects }); - - on_dirty_scheduled = false; + await mathJaxTypeset(contents_div); + current_view.dispatch({ + effects: [ + updateDocBlock.of({ + from, + indent, + delimiter, + contents, + }), + ], + }); }); }; @@ -724,6 +816,9 @@ export const DocBlockPlugin = ViewPlugin.fromClass( } const [contents_div, is_tinymce] = get_contents(target); + // Send updated cursor/scroll info. + startAutosaveTimer(); + // See if this is already a TinyMCE instance; if not, move it // here. if (is_tinymce) { @@ -732,7 +827,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // Wait until the focus event completes; this causes the // cursor position (the selection) to be set in the // contenteditable div. Then, save that location. - setTimeout(() => { + setTimeout(async () => { // Untypeset math in the old doc block and the current doc block before moving its contents around. const tinymce_div = document.getElementById("TinyMCE-inst")!; @@ -740,55 +835,8 @@ export const DocBlockPlugin = ViewPlugin.fromClass( mathJaxUnTypeset(contents_div); // The code which moves TinyMCE into this div disturbs // all the nodes, which 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", - ); - 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!; - selection_path.unshift( - Array.prototype.indexOf.call( - is_first ? p.childNodes : p.children, - current_node, - ), - ); - } - } - + // tied to a specific node. + const sel = saveSelection(); // With the selection saved, it's safe to replace the // contenteditable div with the TinyMCE instance (which // would otherwise wipe the selection). @@ -807,19 +855,17 @@ export const DocBlockPlugin = ViewPlugin.fromClass( null, ); // The previous content edited by TinyMCE is now a div. Retypeset this after the transition. - mathJaxTypeset(old_contents_div); + await mathJaxTypeset(old_contents_div); // Move TinyMCE to the new location, then remove the old // div it will replace. target.insertBefore(tinymce_div, null); // Setting the content makes TinyMCE consider it dirty // -- ignore this "dirty" event. - ignore_next_dirty = true; tinymce_singleton!.setContent(contents_div.innerHTML); - tinymce_singleton!.save(); contents_div.remove(); // The new div is now a TinyMCE editor. Retypeset this. - mathJaxTypeset(tinymce_div); + await mathJaxTypeset(tinymce_div); // This process causes TinyMCE to lose focus. Restore // that. However, this causes TinyMCE to lose the @@ -828,34 +874,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass( // 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 - ); - // Use that to set the selection. - tinymce_singleton!.selection.setCursorLocation( - selection_node, - selection_offset, - ); - } + restoreSelection(sel); }, 0); } return false; @@ -884,6 +903,15 @@ const autosaveExtension = EditorView.updateListener.of( // [ViewUpdate](https://codemirror.net/docs/ref/#view.ViewUpdate) which // describes a change being made to the document. (v: ViewUpdate) => { + // Ignore any transaction group marked with a `noAutosaveAnnotation`. + if ( + v.transactions.some( + (tr) => tr.annotation(noAutosaveAnnotation) === true, + ) + ) { + return true; + } + // The // [docChanged](https://codemirror.net/docs/ref/#view.ViewUpdate.docChanged) // flag is the relevant part of this change description. However, this @@ -908,10 +936,6 @@ const autosaveExtension = EditorView.updateListener.of( set_is_dirty(); startAutosaveTimer(); } else if (v.selectionSet) { - if (ignore_selection_change) { - ignore_selection_change = false; - return; - } // Send an update if only the selection changed. startAutosaveTimer(); } @@ -1076,7 +1100,6 @@ export const CodeMirror_load = async ( if (target_or_false == null) { return false; } - // For some reason, calling this directly omits the most recent edit. Earlier versions of the code didn't have this problem. ??? setTimeout(() => on_dirty(target_or_false)); }); }, @@ -1090,7 +1113,10 @@ export const CodeMirror_load = async ( // from/to values of doc blocks are changed by unfrozen text edits). current_view.dispatch({ changes: codechat_for_web.source.Diff.doc, - annotations: docBlockFreezeAnnotation.of(true), + annotations: [ + docBlockFreezeAnnotation.of(true), + noAutosaveAnnotation.of(true), + ], }); // Now, apply the diff in a separate transaction. Applying them in the // same transaction causes the text edits to modify from/to values in @@ -1118,7 +1144,10 @@ export const CodeMirror_load = async ( } } // Update the view with these changes to the state. - current_view.dispatch({ effects: stateEffects }); + current_view.dispatch({ + effects: stateEffects, + annotations: noAutosaveAnnotation.of(true), + }); } scroll_to_line(cursor_line, scroll_line); }; @@ -1129,8 +1158,10 @@ export const scroll_to_line = (cursor_line?: number, scroll_line?: number) => { return; } - // Create a transaction to set the cursor and scroll position. - const dispatch_data: TransactionSpec = {}; + // Create a transaction to set the cursor and scroll position. Avoid an autosave that sends updated cursor/scroll positions produced by this transaction. + const dispatch_data: TransactionSpec = { + annotations: noAutosaveAnnotation.of(true), + }; if (cursor_line !== undefined) { // Translate the line numbers to a position. const cursor_pos = current_view?.state.doc.line(cursor_line).from; @@ -1153,7 +1184,6 @@ export const scroll_to_line = (cursor_line?: number, scroll_line?: number) => { } // Run it. - ignore_selection_change = true; current_view?.dispatch(dispatch_data); }; diff --git a/client/src/shared_types.mts b/client/src/shared_types.mts index 3a2f73a4..acb75b55 100644 --- a/client/src/shared_types.mts +++ b/client/src/shared_types.mts @@ -16,7 +16,12 @@ // // `shared_types.mts` -- Shared type definitions // ============================================= -// +// The time, in ms, to wait between the last user edit and sending updated data to the Server. +export const autosave_timeout_ms = 300; + +// Produce a whole random number. Fractional numbers aren't consistently converted to the same number. Note that the mantissa of a JavaScript `Number` is 53 bits per the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding). To be certain, also round the result. +export const rand = () => Math.round(Math.random() * 2 ** 53); + // ### Message types // // These mirror the same definitions in the Rust webserver, so that the two can @@ -31,9 +36,10 @@ import { StringDiff } from "./rust-types/StringDiff.js"; import { CodeMirrorDocBlockTuple } from "./rust-types/CodeMirrorDocBlockTuple.js"; import { UpdateMessageContents } from "./rust-types/UpdateMessageContents.js"; import { ResultOkTypes } from "./rust-types/ResultOkTypes.js"; +import { ResultErrTypes } from "./rust-types/ResultErrTypes.js"; // Manually define this, since `ts-rs` can't export `webserver.MessageResult`. -type MessageResult = { Ok: ResultOkTypes } | { Err: string }; +type MessageResult = { Ok: ResultOkTypes } | { Err: ResultErrTypes }; export type { EditorMessageContents, diff --git a/client/src/show_toast.mts b/client/src/show_toast.mts index 0d859382..af6472ba 100644 --- a/client/src/show_toast.mts +++ b/client/src/show_toast.mts @@ -22,7 +22,7 @@ import "toastify-js/src/toastify.css"; export const show_toast = (text: string) => Toastify({ text, - duration: 3000, + duration: 10000, newWindow: true, close: true, gravity: "top", diff --git a/client/src/tinymce-config.mts b/client/src/tinymce-config.mts index 00d813a4..82c15d2e 100644 --- a/client/src/tinymce-config.mts +++ b/client/src/tinymce-config.mts @@ -115,6 +115,12 @@ export const init = async ( // See // [License key](https://www.tiny.cloud/docs/tinymce/latest/license-key). license_key: "gpl", + // Block drag-and-drop of unsupported images and files. See the + // [docs](https://www.tiny.cloud/docs/tinymce/latest/file-image-upload/#block_unsupported_drop). + block_unsupported_drop: true, + // Prevent drag-and-dropping images; this create a mess. See the + // [docs](https://www.tiny.cloud/docs/tinymce/latest/copy-and-paste/#paste_data_images). + paste_data_images: false, // ### Settings for plugins // diff --git a/docs/changelog_0.1.41-math.png b/docs/changelog_0.1.41-math.png new file mode 100644 index 00000000..f398bf9e Binary files /dev/null and b/docs/changelog_0.1.41-math.png differ diff --git a/docs/sample_diagram.drawio.svg b/docs/sample_diagram.drawio.svg new file mode 100644 index 00000000..fbd5f13b --- /dev/null +++ b/docs/sample_diagram.drawio.svg @@ -0,0 +1,4 @@ + + + +
UE 2
coordinator
UE 1
worker
UE 3
worker
gNodeB 1
gNodeB 2
gNodeB 3
SNS server
UE 2
coordinator
UE 1
worker
UE 3
worker
Google NearbyΒ 
(short-range wireless)
CellularΒ data
WiredΒ (internet)
Sender request
Receiver response
Sender
Receiver
\ No newline at end of file diff --git a/extensions/VSCode/Cargo.lock b/extensions/VSCode/Cargo.lock index 8032fca6..ca59d901 100644 --- a/extensions/VSCode/Cargo.lock +++ b/extensions/VSCode/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-files" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0d87f10d70e2948ad40e8edea79c8e77c6c66e0250a4c1f09b690465199576" +checksum = "4009a8beb4dc78a58286ac9d58969ee0a8acecb7912d5ce898b4da4335579341" dependencies = [ "actix-http", "actix-service", @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.12.0" +version = "4.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2233f53f6cb18ae038ce1f0713ca0c72ca0c4b71fe9aaeb59924ce2c89c6dd85" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" dependencies = [ "actix-codec", "actix-http", @@ -206,7 +206,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -353,7 +353,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -445,9 +445,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.46" +version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "jobserver", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -492,9 +492,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -511,7 +511,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -532,6 +532,7 @@ dependencies = [ "actix-web", "actix-web-httpauth", "actix-ws", + "anyhow", "bytes", "chrono", "clap", @@ -603,9 +604,9 @@ checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d" [[package]] name = "convert_case" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db05ffb6856bf0ecdf6367558a76a0e8a77b1713044eb92845c692100ed50190" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -667,9 +668,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffc71fcdcdb40d6f087edddf7f8f1f8f79e6cf922f555a9ee8779752d4819bd" +checksum = "eb230974aaf0aca4d71665bed0aca156cf43b764fcb9583b69c6c3e686f35e72" dependencies = [ "ctor-proc-macro", "dtor", @@ -692,22 +693,24 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.110", + "rustc_version", + "syn 2.0.111", "unicode-xid", ] @@ -736,7 +739,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -947,7 +950,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1034,9 +1037,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1055,8 +1058,9 @@ dependencies = [ [[package]] name = "htmd" -version = "0.3.2" -source = "git+https://github.com/bjones1/htmd.git?branch=fix-faithful-serialization#e13d5a5ed195163c60b0c59974fc5bb521bdd70b" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60ae59466542f2346e43d4a5e9b4432a1fc915b279c9fc0484e9ed7379121454" dependencies = [ "html5ever", "markup5ever_rcdom", @@ -1253,12 +1257,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1338,9 +1342,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -1380,9 +1384,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" @@ -1439,11 +1443,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1518,7 +1522,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1589,9 +1593,9 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "napi" -version = "3.5.2" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e917a98ac74187a5d486604a269ed69cd7901dd4824453d5573fb051f69b1b3" +checksum = "3af30fe8e799dda3a555c496c59e960e4cff1e931b63acbaf3a3b25d9fad22b6" dependencies = [ "bitflags 2.10.0", "ctor", @@ -1611,36 +1615,36 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" [[package]] name = "napi-derive" -version = "3.3.3" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258a6521951715e00568b258b8fb7a44c6087f588c371dc6b84a413f2728fdb" +checksum = "47cffa09ea668c4cc5d7b1198780882e28780ed1804a903b80680725426223d9" dependencies = [ "convert_case", "ctor", "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "napi-derive-backend" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c36636292fe04366a1eec028adc25bc72f4fd7cce35bdcc310499ef74fb7de" +checksum = "5e186227ec22f4675267a176d98dffecb27e6cc88926cbb7efb5427268565c0f" dependencies = [ "convert_case", "proc-macro2", "quote", "semver", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "napi-sys" -version = "3.1.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ef9c1086f16aea2417c3788dbefed7591c3bccd800b827f4dfb271adff1149" +checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" dependencies = [ "libloading", ] @@ -1804,9 +1808,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", "ucd-trie", @@ -1814,9 +1818,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -1824,22 +1828,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "pest_meta" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ "pest", "sha2", @@ -1905,7 +1909,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2151,6 +2155,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2221,7 +2234,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2292,9 +2305,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -2410,9 +2423,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -2427,7 +2440,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2476,7 +2489,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2487,7 +2500,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2581,7 +2594,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2625,9 +2638,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -2637,20 +2650,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", ] @@ -2673,7 +2686,7 @@ checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "termcolor", ] @@ -2851,9 +2864,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -2864,9 +2877,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2874,31 +2887,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -3015,7 +3028,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3026,7 +3039,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3341,28 +3354,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3382,7 +3395,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "synstructure", ] @@ -3416,7 +3429,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] diff --git a/extensions/VSCode/package.json b/extensions/VSCode/package.json index 8723d3cd..9a52f24e 100644 --- a/extensions/VSCode/package.json +++ b/extensions/VSCode/package.json @@ -81,23 +81,23 @@ "devDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", - "@napi-rs/cli": "^3.4.1", + "@napi-rs/cli": "^3.5.0", "@tybys/wasm-util": "^0.10.1", "@types/escape-html": "^1.0.4", "@types/node": "^24.10.1", "@types/vscode": "1.61.0", - "@typescript-eslint/eslint-plugin": "^8.46.4", - "@typescript-eslint/parser": "^8.46.4", - "@vscode/vsce": "^3.7.0", + "@typescript-eslint/eslint-plugin": "^8.48.1", + "@typescript-eslint/parser": "^8.48.1", + "@vscode/vsce": "^3.7.1", "chalk": "^5.6.2", - "esbuild": "^0.27.0", + "esbuild": "^0.27.1", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-node": "^11.1.0", "npm-run-all2": "^8.0.4", - "ovsx": "^0.10.6", - "prettier": "^3.6.2", + "ovsx": "^0.10.7", + "prettier": "^3.7.4", "typescript": "^5.9.3" }, "optionalDependencies": { diff --git a/extensions/VSCode/pnpm-lock.yaml b/extensions/VSCode/pnpm-lock.yaml index a02d9e62..8c3838d7 100644 --- a/extensions/VSCode/pnpm-lock.yaml +++ b/extensions/VSCode/pnpm-lock.yaml @@ -19,8 +19,8 @@ importers: specifier: ^1.7.1 version: 1.7.1 '@napi-rs/cli': - specifier: ^3.4.1 - version: 3.4.1(@emnapi/runtime@1.7.1)(@types/node@24.10.1) + specifier: ^3.5.0 + version: 3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.1) '@tybys/wasm-util': specifier: ^0.10.1 version: 0.10.1 @@ -34,20 +34,20 @@ importers: specifier: 1.61.0 version: 1.61.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.46.4 - version: 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.46.4 - version: 8.46.4(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.48.1 + version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) '@vscode/vsce': - specifier: ^3.7.0 - version: 3.7.0 + specifier: ^3.7.1 + version: 3.7.1 chalk: specifier: ^5.6.2 version: 5.6.2 esbuild: - specifier: ^0.27.0 - version: 0.27.0 + specifier: ^0.27.1 + version: 0.27.1 eslint: specifier: ^9.39.1 version: 9.39.1 @@ -56,7 +56,7 @@ importers: version: 10.1.8(eslint@9.39.1) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + version: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) eslint-plugin-node: specifier: ^11.1.0 version: 11.1.0(eslint@9.39.1) @@ -64,11 +64,11 @@ importers: specifier: ^8.0.4 version: 8.0.4 ovsx: - specifier: ^0.10.6 - version: 0.10.6 + specifier: ^0.10.7 + version: 0.10.7 prettier: - specifier: ^3.6.2 - version: 3.6.2 + specifier: ^3.7.4 + version: 3.7.4 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -117,16 +117,16 @@ packages: resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==} engines: {node: '>=20.0.0'} - '@azure/msal-browser@4.26.1': - resolution: {integrity: sha512-GGCIsZXxyNm5QcQZ4maA9q+9UWmM+/87G+ybvPkrE32el1URSa9WYt0t67ks3/P0gspZX9RoEqyLqJ/X/JDnBQ==} + '@azure/msal-browser@4.26.2': + resolution: {integrity: sha512-F2U1mEAFsYGC5xzo1KuWc/Sy3CRglU9Ql46cDUx8x/Y3KnAIr1QAq96cIKCk/ZfnVxlvprXWRjNKoEpgLJXLhg==} engines: {node: '>=0.8.0'} - '@azure/msal-common@15.13.1': - resolution: {integrity: sha512-vQYQcG4J43UWgo1lj7LcmdsGUKWYo28RfEvDQAEMmQIMjSFufvb+pS0FJ3KXmrPmnWlt1vHDl3oip6mIDUQ4uA==} + '@azure/msal-common@15.13.2': + resolution: {integrity: sha512-cNwUoCk3FF8VQ7Ln/MdcJVIv3sF73/OT86cRH81ECsydh7F4CNfIo2OAx6Cegtg8Yv75x4506wN4q+Emo6erOA==} engines: {node: '>=0.8.0'} - '@azure/msal-node@3.8.2': - resolution: {integrity: sha512-dQrex2LiXwlCe9WuBHnCsY+xxLyuMXSd2SDEYJuhqB7cE8u6QafiC1xy8j8eBjGO30AsRi2M6amH0ZKk7vJpjA==} + '@azure/msal-node@3.8.3': + resolution: {integrity: sha512-Ul7A4gwmaHzYWj2Z5xBDly/W8JSC1vnKgJ898zPMZr0oSf1ah0tiL15sytjycU/PMhDZAlkWtEL1+MzNMU6uww==} engines: {node: '>=16'} '@babel/code-frame@7.27.1': @@ -146,158 +146,158 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + '@esbuild/aix-ppc64@0.27.1': + resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/android-arm64@0.27.1': + resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm@0.27.1': + resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-x64@0.27.1': + resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/darwin-arm64@0.27.1': + resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-x64@0.27.1': + resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/freebsd-arm64@0.27.1': + resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-x64@0.27.1': + resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/linux-arm64@0.27.1': + resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm@0.27.1': + resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-ia32@0.27.1': + resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-loong64@0.27.1': + resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-mips64el@0.27.1': + resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-ppc64@0.27.1': + resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + '@esbuild/linux-riscv64@0.27.1': + resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + '@esbuild/linux-s390x@0.27.1': + resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + '@esbuild/linux-x64@0.27.1': + resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + '@esbuild/netbsd-arm64@0.27.1': + resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + '@esbuild/netbsd-x64@0.27.1': + resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + '@esbuild/openbsd-arm64@0.27.1': + resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + '@esbuild/openbsd-x64@0.27.1': + resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + '@esbuild/openharmony-arm64@0.27.1': + resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + '@esbuild/sunos-x64@0.27.1': + resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + '@esbuild/win32-arm64@0.27.1': + resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + '@esbuild/win32-ia32@0.27.1': + resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + '@esbuild/win32-x64@0.27.1': + resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -324,8 +324,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.1': @@ -356,134 +356,134 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} - engines: {node: '>=18'} + '@inquirer/ansi@2.0.2': + resolution: {integrity: sha512-SYLX05PwJVnW+WVegZt1T4Ip1qba1ik+pNJPDiqvk6zS5Y/i8PhRzLpGEtVd7sW0G8cMtkD8t4AZYhQwm8vnww==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/checkbox@4.3.2': - resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} - engines: {node: '>=18'} + '@inquirer/checkbox@5.0.2': + resolution: {integrity: sha512-iTPV4tMMct7iOpwer5qmTP7gjnk1VQJjsNfAaC2b8Q3qiuHM3K2yjjDr5u1MKfkrvp2JD4Flf8sIPpF21pmZmw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/confirm@5.1.21': - resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} - engines: {node: '>=18'} + '@inquirer/confirm@6.0.2': + resolution: {integrity: sha512-A0/13Wyi+8iFeNDX6D4zZYKPoBLIEbE4K/219qHcnpXMer2weWvaTo63+2c7mQPPA206DEMSYVOPnEw3meOlCw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} - engines: {node: '>=18'} + '@inquirer/core@11.0.2': + resolution: {integrity: sha512-lgMRx/n02ciiNELBvFLHtmcjbV5tf5D/I0UYfCg2YbTZWmBZ10/niLd3IjWBxz8LtM27xP+4oLEa06Slmb7p7A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/editor@4.2.23': - resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} - engines: {node: '>=18'} + '@inquirer/editor@5.0.2': + resolution: {integrity: sha512-pXQ4Nf0qmFcJuYB6NlcIIxH6l6zKOwNg1Jh/ZRdKd2dTqBB4OXKUFbFwR2K4LVXVtq15ZFFatBVT+rerYR8hWQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/expand@4.0.23': - resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} - engines: {node: '>=18'} + '@inquirer/expand@5.0.2': + resolution: {integrity: sha512-siFG1swxfjFIOxIcehtZkh+KUNB/YCpyfHNEGu+nC/SBXIbgUWibvThLn/WesSxLRGOeSKdNKoTm+GQCKFm6Ww==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/external-editor@1.0.3': - resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} - engines: {node: '>=18'} + '@inquirer/external-editor@2.0.2': + resolution: {integrity: sha512-X/fMXK7vXomRWEex1j8mnj7s1mpnTeP4CO/h2gysJhHLT2WjBnLv4ZQEGpm/kcYI8QfLZ2fgW+9kTKD+jeopLg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} + '@inquirer/figures@2.0.2': + resolution: {integrity: sha512-qXm6EVvQx/FmnSrCWCIGtMHwqeLgxABP8XgcaAoywsL0NFga9gD5kfG0gXiv80GjK9Hsoz4pgGwF/+CjygyV9A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/input@4.3.1': - resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} - engines: {node: '>=18'} + '@inquirer/input@5.0.2': + resolution: {integrity: sha512-hN2YRo1QiEc9lD3mK+CPnTS4TK2RhCMmMmP4nCWwTkmQL2vx9jPJWYk+rbUZpwR1D583ZJk1FI3i9JZXIpi/qg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/number@3.0.23': - resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} - engines: {node: '>=18'} + '@inquirer/number@4.0.2': + resolution: {integrity: sha512-4McnjTSYrlthNW1ojkkmP75WLRYhQs7GXm6pDDoIrHqJuV5uUYwfdbB0geHdaKMarAqJQgoOVjzIT0jdWCsKew==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/password@4.0.23': - resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} - engines: {node: '>=18'} + '@inquirer/password@5.0.2': + resolution: {integrity: sha512-oSDziMKiw4G2e4zS+0JRfxuPFFGh6N/9yUaluMgEHp2/Yyj2JGwfDO7XbwtOrxVrz+XsP/iaGyWXdQb9d8A0+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/prompts@7.10.1': - resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} - engines: {node: '>=18'} + '@inquirer/prompts@8.0.2': + resolution: {integrity: sha512-2zK5zY48fZcl6+gG4eqOC/UzZsJckHCRvjXoLuW4D8LKOCVGdcJiSKkLnumSZjR/6PXPINDGOrGHqNxb+sxJDg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/rawlist@4.1.11': - resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} - engines: {node: '>=18'} + '@inquirer/rawlist@5.0.2': + resolution: {integrity: sha512-AcNALEdQKUQDeJcpC1a3YC53m1MLv+sMUS+vRZ8Qigs1Yg3Dcdtmi82rscJplogKOY8CXkKW4wvVwHS2ZjCIBQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/search@3.2.2': - resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} - engines: {node: '>=18'} + '@inquirer/search@4.0.2': + resolution: {integrity: sha512-hg63w5toohdzE65S3LiGhdfIL0kT+yisbZARf7zw65PvyMUTutTN3eMAvD/B6y/25z88vTrB7kSB45Vz5CbrXg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/select@4.4.2': - resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} - engines: {node: '>=18'} + '@inquirer/select@5.0.2': + resolution: {integrity: sha512-JygTohvQxSNnvt7IKANVlg/eds+yN5sLRilYeGc4ri/9Aqi/2QPoXBMV5Cz/L1VtQv63SnTbPXJZeCK2pSwsOA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} - engines: {node: '>=18'} + '@inquirer/type@4.0.2': + resolution: {integrity: sha512-cae7mzluplsjSdgFA6ACLygb5jC8alO0UUnFPyu0E7tNRPrL+q/f8VcSXp+cjZQ7l5CMpDpi2G1+IQvkOiL1Lw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -502,12 +502,12 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@napi-rs/cli@3.4.1': - resolution: {integrity: sha512-ayhm+NfrP5Hmh7vy5pfyYm/ktYtLh2PrgdLuqHTAubO7RoO2JkUE4F991AtgYxNewwXI8+guZLxU8itV7QnDrQ==} + '@napi-rs/cli@3.5.0': + resolution: {integrity: sha512-bJsDvAa9qK9VMkFhr780XWfQlK+GDlAX8qpK20buSmA0ld6nxCtiZ5a0J45zbd0FWT+VTZE1/u8VPH2vLfnVvw==} engines: {node: '>= 16'} hasBin: true peerDependencies: - '@emnapi/runtime': ^1.5.0 + '@emnapi/runtime': ^1.7.1 peerDependenciesMeta: '@emnapi/runtime': optional: true @@ -754,8 +754,8 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.0.7': - resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + '@napi-rs/wasm-runtime@1.1.0': + resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': resolution: {integrity: sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==} @@ -1041,20 +1041,20 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} - '@textlint/ast-node-types@15.4.0': - resolution: {integrity: sha512-IqY8i7IOGuvy05wZxISB7Me1ZyrvhaQGgx6DavfQjH3cfwpPFdDbDYmMXMuSv2xLS1kDB1kYKBV7fL2Vi16lRA==} + '@textlint/ast-node-types@15.4.1': + resolution: {integrity: sha512-XifMpBMdo0E1Fuh85YdcYAgy+okNg9WKBzIPIO4JUDnSWUVFihnogrM4cjDapeHkgzSgulwR8oJVJ17eyxI1bA==} - '@textlint/linter-formatter@15.4.0': - resolution: {integrity: sha512-rfqOZmnI1Wwc/Pa4LK+vagvVPmvxf9oRsBRqIOB04DwhucingZyAIJI/TyG18DIDYbP2aFXBZ3oOvyAxHe/8PQ==} + '@textlint/linter-formatter@15.4.1': + resolution: {integrity: sha512-kAV7Sup3vwvqxKvBbf9lx/JaPHkRybQp/LLvA73U1AorPZE6XyfBAFG24BbMiCs4OX1ax4g7kXRuFPgMLWRf+g==} - '@textlint/module-interop@15.4.0': - resolution: {integrity: sha512-uGf+SFIfzOLCbZI0gp+2NLsrkSArsvEWulPP6lJuKp7yRHadmy7Xf/YHORe46qhNyyxc8PiAfiixHJSaHGUrGg==} + '@textlint/module-interop@15.4.1': + resolution: {integrity: sha512-jHtM2E5CR68P3z/+FGrEU5pml2fQVzEo2sez9FEjrVHSPCrHtqHcPaKfsYbQJjc9C48ObwaWrCzRNaL3KedNCQ==} - '@textlint/resolver@15.4.0': - resolution: {integrity: sha512-Vh/QceKZQHFJFG4GxxIsKM1Xhwv93mbtKHmFE5/ybal1mIKHdqF03Z9Guaqt6Sx/AeNUshq0hkMOEhEyEWnehQ==} + '@textlint/resolver@15.4.1': + resolution: {integrity: sha512-uVssyG3XXXKNY+O7NOajGvQZTyOuhPviwlq7Xek6ZT9K1eDQtA8074cPkAQoLMYhi/TUyOE5P5kpz42UF8Lmdw==} - '@textlint/types@15.4.0': - resolution: {integrity: sha512-ZMwJgw/xjxJufOD+IB7I2Enl9Si4Hxo04B76RwUZ5cKBKzOPcmd6WvGe2F7jqdgmTdGnfMU+Bo/joQrjPNIWqg==} + '@textlint/types@15.4.1': + resolution: {integrity: sha512-WByVZ3zblbvuI+voWQplUP7seSTKXI9z6TMVXEB3dY3JFrZCIXWKNfLbETX5lZV7fYkCMaDtILO1l6s11wdbQA==} '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1083,63 +1083,63 @@ packages: '@types/vscode@1.61.0': resolution: {integrity: sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==} - '@typescript-eslint/eslint-plugin@8.46.4': - resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==} + '@typescript-eslint/eslint-plugin@8.48.1': + resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.46.4 + '@typescript-eslint/parser': ^8.48.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.46.4': - resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==} + '@typescript-eslint/parser@8.48.1': + resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} 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.46.4': - resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==} + '@typescript-eslint/project-service@8.48.1': + resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.46.4': - resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==} + '@typescript-eslint/scope-manager@8.48.1': + resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.46.4': - resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==} + '@typescript-eslint/tsconfig-utils@8.48.1': + resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.46.4': - resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==} + '@typescript-eslint/type-utils@8.48.1': + resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} 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.46.4': - resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==} + '@typescript-eslint/types@8.48.1': + resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.46.4': - resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==} + '@typescript-eslint/typescript-estree@8.48.1': + resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.46.4': - resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==} + '@typescript-eslint/utils@8.48.1': + resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} 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.46.4': - resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==} + '@typescript-eslint/visitor-keys@8.48.1': + resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typespec/ts-http-runtime@0.3.2': @@ -1194,8 +1194,8 @@ packages: '@vscode/vsce-sign@2.0.9': resolution: {integrity: sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==} - '@vscode/vsce@3.7.0': - resolution: {integrity: sha512-LY9r2T4joszRjz4d92ZPl6LTBUPS4IWH9gG/3JUv+1QyBJrveZlcVISuiaq0EOpmcgFh0GgVgKD4rD/21Tu8sA==} + '@vscode/vsce@3.7.1': + resolution: {integrity: sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g==} engines: {node: '>= 20'} hasBin: true @@ -1239,9 +1239,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1532,6 +1529,9 @@ packages: node-addon-api: optional: true + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1587,8 +1587,8 @@ packages: es-toolkit@1.42.0: resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==} - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + esbuild@0.27.1: + resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} hasBin: true @@ -1685,11 +1685,6 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -1735,6 +1730,15 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1796,6 +1800,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1819,8 +1827,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} hasBin: true @@ -2080,10 +2088,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true - js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -2183,8 +2187,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} lru-cache@6.0.0: @@ -2258,9 +2262,9 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -2279,8 +2283,8 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-sarif-builder@3.3.0: - resolution: {integrity: sha512-8taRy2nQs1xNs8iO2F0XbkZJEliiijpKgFVcyiwKjJ2+3X59LVI3wY84qRdJwRDpIo5GK8wvb1pxcJ+JVu3jrg==} + node-sarif-builder@3.3.1: + resolution: {integrity: sha512-8z5dAbhpxmk/WRQHXlv4V0h+9Y4Ugk+w08lyhV/7E/CQX9yDdBc3025/EG+RSMJU2aPFh/IQ7XDV7Ti5TLt/TA==} engines: {node: '>=20'} normalize-package-data@6.0.2: @@ -2323,6 +2327,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2334,8 +2341,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ovsx@0.10.6: - resolution: {integrity: sha512-MZ7pgQ+IS5kumAfZGnhEjmdOUwW0UlmlekMwuA5DeUJeft7jFu9fTIEhH71ypjdUSpdqchodoKgb5y/ilh7b5g==} + ovsx@0.10.7: + resolution: {integrity: sha512-UjBQlB5xSDD+biAylCZ8Q/k3An9K3y9FYa+hT/HTbJkzOQP+gaNHX20CaOo4lrYT1iJXdiePH9zS2uvCXdDNDA==} engines: {node: '>= 20'} hasBin: true @@ -2436,8 +2443,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true @@ -2631,9 +2638,6 @@ packages: spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -2646,6 +2650,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -2718,6 +2726,10 @@ packages: resolution: {integrity: sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==} engines: {node: '>=4'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tmp@0.2.5: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} @@ -2870,10 +2882,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2882,6 +2890,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2914,10 +2926,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - snapshots: '@azu/format-text@1.0.2': {} @@ -2983,8 +2991,8 @@ snapshots: '@azure/core-tracing': 1.3.1 '@azure/core-util': 1.13.1 '@azure/logger': 1.3.0 - '@azure/msal-browser': 4.26.1 - '@azure/msal-node': 3.8.2 + '@azure/msal-browser': 4.26.2 + '@azure/msal-node': 3.8.3 open: 10.2.0 tslib: 2.8.1 transitivePeerDependencies: @@ -2997,15 +3005,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@azure/msal-browser@4.26.1': + '@azure/msal-browser@4.26.2': dependencies: - '@azure/msal-common': 15.13.1 + '@azure/msal-common': 15.13.2 - '@azure/msal-common@15.13.1': {} + '@azure/msal-common@15.13.2': {} - '@azure/msal-node@3.8.2': + '@azure/msal-node@3.8.3': dependencies: - '@azure/msal-common': 15.13.1 + '@azure/msal-common': 15.13.2 jsonwebtoken: 9.0.2 uuid: 8.3.2 @@ -3030,82 +3038,82 @@ snapshots: dependencies: tslib: 2.8.1 - '@esbuild/aix-ppc64@0.27.0': + '@esbuild/aix-ppc64@0.27.1': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/android-arm64@0.27.1': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/android-arm@0.27.1': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/android-x64@0.27.1': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/darwin-arm64@0.27.1': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/darwin-x64@0.27.1': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/freebsd-arm64@0.27.1': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/freebsd-x64@0.27.1': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/linux-arm64@0.27.1': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/linux-arm@0.27.1': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/linux-ia32@0.27.1': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/linux-loong64@0.27.1': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/linux-mips64el@0.27.1': optional: true - '@esbuild/linux-ppc64@0.27.0': + '@esbuild/linux-ppc64@0.27.1': optional: true - '@esbuild/linux-riscv64@0.27.0': + '@esbuild/linux-riscv64@0.27.1': optional: true - '@esbuild/linux-s390x@0.27.0': + '@esbuild/linux-s390x@0.27.1': optional: true - '@esbuild/linux-x64@0.27.0': + '@esbuild/linux-x64@0.27.1': optional: true - '@esbuild/netbsd-arm64@0.27.0': + '@esbuild/netbsd-arm64@0.27.1': optional: true - '@esbuild/netbsd-x64@0.27.0': + '@esbuild/netbsd-x64@0.27.1': optional: true - '@esbuild/openbsd-arm64@0.27.0': + '@esbuild/openbsd-arm64@0.27.1': optional: true - '@esbuild/openbsd-x64@0.27.0': + '@esbuild/openbsd-x64@0.27.1': optional: true - '@esbuild/openharmony-arm64@0.27.0': + '@esbuild/openharmony-arm64@0.27.1': optional: true - '@esbuild/sunos-x64@0.27.0': + '@esbuild/sunos-x64@0.27.1': optional: true - '@esbuild/win32-arm64@0.27.0': + '@esbuild/win32-arm64@0.27.1': optional: true - '@esbuild/win32-ia32@0.27.0': + '@esbuild/win32-ia32@0.27.1': optional: true - '@esbuild/win32-x64@0.27.0': + '@esbuild/win32-x64@0.27.1': optional: true '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': @@ -3131,7 +3139,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 debug: 4.4.3 @@ -3165,128 +3173,122 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@1.0.2': {} + '@inquirer/ansi@2.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@24.10.1)': + '@inquirer/checkbox@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/confirm@5.1.21(@types/node@24.10.1)': + '@inquirer/confirm@6.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/core@10.3.2(@types/node@24.10.1)': + '@inquirer/core@11.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/ansi': 2.0.2 + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.1) cli-width: 4.1.0 - mute-stream: 2.0.0 + mute-stream: 3.0.0 signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 + wrap-ansi: 9.0.2 optionalDependencies: '@types/node': 24.10.1 - '@inquirer/editor@4.2.23(@types/node@24.10.1)': + '@inquirer/editor@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/external-editor': 1.0.3(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/external-editor': 2.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/expand@4.0.23(@types/node@24.10.1)': + '@inquirer/expand@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/external-editor@1.0.3(@types/node@24.10.1)': + '@inquirer/external-editor@2.0.2(@types/node@24.10.1)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: '@types/node': 24.10.1 - '@inquirer/figures@1.0.15': {} + '@inquirer/figures@2.0.2': {} - '@inquirer/input@4.3.1(@types/node@24.10.1)': + '@inquirer/input@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/number@3.0.23(@types/node@24.10.1)': + '@inquirer/number@4.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/password@4.0.23(@types/node@24.10.1)': + '@inquirer/password@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/prompts@7.10.1(@types/node@24.10.1)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.10.1) - '@inquirer/confirm': 5.1.21(@types/node@24.10.1) - '@inquirer/editor': 4.2.23(@types/node@24.10.1) - '@inquirer/expand': 4.0.23(@types/node@24.10.1) - '@inquirer/input': 4.3.1(@types/node@24.10.1) - '@inquirer/number': 3.0.23(@types/node@24.10.1) - '@inquirer/password': 4.0.23(@types/node@24.10.1) - '@inquirer/rawlist': 4.1.11(@types/node@24.10.1) - '@inquirer/search': 3.2.2(@types/node@24.10.1) - '@inquirer/select': 4.4.2(@types/node@24.10.1) + '@inquirer/prompts@8.0.2(@types/node@24.10.1)': + dependencies: + '@inquirer/checkbox': 5.0.2(@types/node@24.10.1) + '@inquirer/confirm': 6.0.2(@types/node@24.10.1) + '@inquirer/editor': 5.0.2(@types/node@24.10.1) + '@inquirer/expand': 5.0.2(@types/node@24.10.1) + '@inquirer/input': 5.0.2(@types/node@24.10.1) + '@inquirer/number': 4.0.2(@types/node@24.10.1) + '@inquirer/password': 5.0.2(@types/node@24.10.1) + '@inquirer/rawlist': 5.0.2(@types/node@24.10.1) + '@inquirer/search': 4.0.2(@types/node@24.10.1) + '@inquirer/select': 5.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/rawlist@4.1.11(@types/node@24.10.1)': + '@inquirer/rawlist@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/search@3.2.2(@types/node@24.10.1)': + '@inquirer/search@4.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/select@4.4.2(@types/node@24.10.1)': + '@inquirer/select@5.0.2(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.0.2(@types/node@24.10.1) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 - '@inquirer/type@3.0.10(@types/node@24.10.1)': + '@inquirer/type@4.0.2(@types/node@24.10.1)': optionalDependencies: '@types/node': 24.10.1 @@ -3305,18 +3307,18 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@napi-rs/cli@3.4.1(@emnapi/runtime@1.7.1)(@types/node@24.10.1)': + '@napi-rs/cli@3.5.0(@emnapi/runtime@1.7.1)(@types/node@24.10.1)': dependencies: - '@inquirer/prompts': 7.10.1(@types/node@24.10.1) + '@inquirer/prompts': 8.0.2(@types/node@24.10.1) '@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 - debug: 4.4.3 emnapi: 1.7.1 es-toolkit: 1.42.0 js-yaml: 4.1.1 + obug: 2.1.1 semver: 7.7.3 typanion: 3.14.0 optionalDependencies: @@ -3385,7 +3387,7 @@ snapshots: '@napi-rs/lzma-wasm32-wasi@1.4.5': dependencies: - '@napi-rs/wasm-runtime': 1.0.7 + '@napi-rs/wasm-runtime': 1.1.0 optional: true '@napi-rs/lzma-win32-arm64-msvc@1.4.5': @@ -3455,7 +3457,7 @@ snapshots: '@napi-rs/tar-wasm32-wasi@1.1.0': dependencies: - '@napi-rs/wasm-runtime': 1.0.7 + '@napi-rs/wasm-runtime': 1.1.0 optional: true '@napi-rs/tar-win32-arm64-msvc@1.1.0': @@ -3493,7 +3495,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.0.7': + '@napi-rs/wasm-runtime@1.1.0': dependencies: '@emnapi/core': 1.7.1 '@emnapi/runtime': 1.7.1 @@ -3529,7 +3531,7 @@ snapshots: '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': dependencies: - '@napi-rs/wasm-runtime': 1.0.7 + '@napi-rs/wasm-runtime': 1.1.0 optional: true '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': @@ -3722,9 +3724,9 @@ snapshots: dependencies: '@secretlint/resolver': 10.2.2 '@secretlint/types': 10.2.2 - '@textlint/linter-formatter': 15.4.0 - '@textlint/module-interop': 15.4.0 - '@textlint/types': 15.4.0 + '@textlint/linter-formatter': 15.4.1 + '@textlint/module-interop': 15.4.1 + '@textlint/types': 15.4.1 chalk: 5.6.2 debug: 4.4.3 pluralize: 8.0.0 @@ -3753,7 +3755,7 @@ snapshots: '@secretlint/secretlint-formatter-sarif@10.2.2': dependencies: - node-sarif-builder: 3.3.0 + node-sarif-builder: 3.3.1 '@secretlint/secretlint-rule-no-dotenv@10.2.2': dependencies: @@ -3770,18 +3772,18 @@ snapshots: '@sindresorhus/merge-streams@2.3.0': {} - '@textlint/ast-node-types@15.4.0': {} + '@textlint/ast-node-types@15.4.1': {} - '@textlint/linter-formatter@15.4.0': + '@textlint/linter-formatter@15.4.1': dependencies: '@azu/format-text': 1.0.2 '@azu/style-format': 1.0.1 - '@textlint/module-interop': 15.4.0 - '@textlint/resolver': 15.4.0 - '@textlint/types': 15.4.0 + '@textlint/module-interop': 15.4.1 + '@textlint/resolver': 15.4.1 + '@textlint/types': 15.4.1 chalk: 4.1.2 debug: 4.4.3 - js-yaml: 3.14.2 + js-yaml: 4.1.1 lodash: 4.17.21 pluralize: 2.0.0 string-width: 4.2.3 @@ -3791,13 +3793,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@textlint/module-interop@15.4.0': {} + '@textlint/module-interop@15.4.1': {} - '@textlint/resolver@15.4.0': {} + '@textlint/resolver@15.4.1': {} - '@textlint/types@15.4.0': + '@textlint/types@15.4.1': dependencies: - '@textlint/ast-node-types': 15.4.0 + '@textlint/ast-node-types': 15.4.1 '@tybys/wasm-util@0.10.1': dependencies: @@ -3821,14 +3823,14 @@ snapshots: '@types/vscode@1.61.0': {} - '@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 eslint: 9.39.1 graphemer: 1.4.0 ignore: 7.0.5 @@ -3838,41 +3840,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3 eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.46.4(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) - '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.46.4': + '@typescript-eslint/scope-manager@8.48.1': dependencies: - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 - '@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3880,38 +3882,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.46.4': {} + '@typescript-eslint/types@8.48.1': {} - '@typescript-eslint/typescript-estree@8.46.4(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.46.4(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.46.4': + '@typescript-eslint/visitor-keys@8.48.1': dependencies: - '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/types': 8.48.1 eslint-visitor-keys: 4.2.1 '@typespec/ts-http-runtime@0.3.2': @@ -3961,7 +3962,7 @@ snapshots: '@vscode/vsce-sign-win32-arm64': 2.0.6 '@vscode/vsce-sign-win32-x64': 2.0.6 - '@vscode/vsce@3.7.0': + '@vscode/vsce@3.7.1': dependencies: '@azure/identity': 4.13.0 '@secretlint/node': 10.2.2 @@ -3975,7 +3976,7 @@ snapshots: cockatiel: 3.2.1 commander: 12.1.0 form-data: 4.0.5 - glob: 11.0.3 + glob: 11.1.0 hosted-git-info: 4.1.0 jsonc-parser: 3.3.1 leven: 3.1.0 @@ -4033,10 +4034,6 @@ snapshots: ansi-styles@6.2.3: {} - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - argparse@2.0.1: {} array-buffer-byte-length@1.0.2: @@ -4358,6 +4355,8 @@ snapshots: emnapi@1.7.1: {} + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -4462,34 +4461,34 @@ snapshots: es-toolkit@1.42.0: {} - esbuild@0.27.0: + esbuild@0.27.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 + '@esbuild/aix-ppc64': 0.27.1 + '@esbuild/android-arm': 0.27.1 + '@esbuild/android-arm64': 0.27.1 + '@esbuild/android-x64': 0.27.1 + '@esbuild/darwin-arm64': 0.27.1 + '@esbuild/darwin-x64': 0.27.1 + '@esbuild/freebsd-arm64': 0.27.1 + '@esbuild/freebsd-x64': 0.27.1 + '@esbuild/linux-arm': 0.27.1 + '@esbuild/linux-arm64': 0.27.1 + '@esbuild/linux-ia32': 0.27.1 + '@esbuild/linux-loong64': 0.27.1 + '@esbuild/linux-mips64el': 0.27.1 + '@esbuild/linux-ppc64': 0.27.1 + '@esbuild/linux-riscv64': 0.27.1 + '@esbuild/linux-s390x': 0.27.1 + '@esbuild/linux-x64': 0.27.1 + '@esbuild/netbsd-arm64': 0.27.1 + '@esbuild/netbsd-x64': 0.27.1 + '@esbuild/openbsd-arm64': 0.27.1 + '@esbuild/openbsd-x64': 0.27.1 + '@esbuild/openharmony-arm64': 0.27.1 + '@esbuild/sunos-x64': 0.27.1 + '@esbuild/win32-arm64': 0.27.1 + '@esbuild/win32-ia32': 0.27.1 + '@esbuild/win32-x64': 0.27.1 escape-html@1.0.3: {} @@ -4507,11 +4506,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(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.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -4523,7 +4522,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4534,7 +4533,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(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.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4546,7 +4545,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4584,7 +4583,7 @@ snapshots: '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 @@ -4622,8 +4621,6 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 - esprima@4.0.1: {} - esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -4665,6 +4662,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4728,6 +4729,8 @@ snapshots: generator-function@2.0.1: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4763,7 +4766,7 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@11.0.3: + glob@11.1.0: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 @@ -5027,11 +5030,6 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.14.2: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -5131,7 +5129,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.2: {} + lru-cache@11.2.4: {} lru-cache@6.0.0: dependencies: @@ -5193,7 +5191,7 @@ snapshots: mute-stream@0.0.8: {} - mute-stream@2.0.0: {} + mute-stream@3.0.0: {} napi-build-utils@2.0.0: optional: true @@ -5211,7 +5209,7 @@ snapshots: node-gyp-build@4.8.4: optional: true - node-sarif-builder@3.3.0: + node-sarif-builder@3.3.1: dependencies: '@types/sarif': 2.1.7 fs-extra: 11.3.2 @@ -5272,6 +5270,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + obug@2.1.1: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -5293,9 +5293,9 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ovsx@0.10.6: + ovsx@0.10.7: dependencies: - '@vscode/vsce': 3.7.0 + '@vscode/vsce': 3.7.1 commander: 6.2.1 follow-redirects: 1.15.11 is-ci: 2.0.0 @@ -5360,7 +5360,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.2 + lru-cache: 11.2.4 minipass: 7.1.2 path-type@6.0.0: {} @@ -5399,7 +5399,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.6.2: {} + prettier@3.7.4: {} pump@3.0.3: dependencies: @@ -5635,8 +5635,6 @@ snapshots: spdx-license-ids@3.0.22: {} - sprintf-js@1.0.3: {} - stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -5654,6 +5652,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -5748,6 +5752,11 @@ snapshots: dependencies: editions: 6.22.0 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tmp@0.2.5: {} to-regex-range@5.0.1: @@ -5921,12 +5930,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -5939,6 +5942,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrappy@1.0.2: optional: true @@ -5971,5 +5980,3 @@ snapshots: buffer-crc32: 0.2.13 yocto-queue@0.1.0: {} - - yoctocolors-cjs@2.1.3: {} diff --git a/extensions/VSCode/src/extension.ts b/extensions/VSCode/src/extension.ts index 4ae3ea93..81f049e7 100644 --- a/extensions/VSCode/src/extension.ts +++ b/extensions/VSCode/src/extension.ts @@ -40,14 +40,17 @@ import { CodeChatEditorServer, initServer } from "./index"; // ### Local packages import { + autosave_timeout_ms, EditorMessage, MessageResult, + rand, UpdateMessageContents, } from "../../../client/src/shared_types.mjs"; import { DEBUG_ENABLED, MAX_MESSAGE_LENGTH, } from "../../../client/src/debug_enabled.mjs"; +import { ResultErrTypes } from "../../../client/src/rust-types/ResultErrTypes"; // Globals // ----------------------------------------------------------------------------- @@ -93,6 +96,8 @@ let quiet_next_error = false; // True if the editor contents have changed (are dirty) from the perspective of // the CodeChat Editor (not if the contents are saved to disk). let is_dirty = false; +// The version of the current file. +let version = 0.0; // An object to start/stop the CodeChat Editor Server. let codeChatEditorServer: CodeChatEditorServer | undefined; @@ -323,21 +328,22 @@ export const activate = (context: vscode.ExtensionContext) => { value as UpdateMessageContents; const doc = get_document(current_update.file_path); if (doc === undefined) { - sendResult( - id, - `No open document for ${current_update.file_path}`, - ); + await sendResult(id, { + NoOpenDocument: current_update.file_path, + }); break; } if (current_update.contents !== undefined) { const source = current_update.contents.source; - // Is this plain text, or a diff? This will - // produce a change event, which we'll ignore. + // This will + // produce a change event, which we'll ignore. The change may also produce a selection change, which should also be ignored. ignore_text_document_change = true; + ignore_selection_change = true; // Use a workspace edit, since calls to // `TextEditor.edit` must be made to the active // editor only. const wse = new vscode.WorkspaceEdit(); + // Is this plain text, or a diff? if ("Plain" in source) { wse.replace( doc.uri, @@ -353,6 +359,13 @@ export const activate = (context: vscode.ExtensionContext) => { ); } else { assert("Diff" in source); + // If this diff was not made against the text we currently have, reject it. + if (source.Diff.version !== version) { + await sendResult(id, "OutOfSync"); + // Send an `Update` with the full text to re-sync the Client. + send_update(true); + break; + } const diffs = source.Diff.doc; for (const diff of diffs) { // Convert from character offsets from the @@ -378,12 +391,12 @@ export const activate = (context: vscode.ExtensionContext) => { } } } - vscode.workspace - .applyEdit(wse) - .then( - () => - (ignore_text_document_change = false), - ); + vscode.workspace.applyEdit(wse).then(() => { + ignore_text_document_change = false; + ignore_selection_change = false; + }); + // Now that we've updated our text, update the associated version as well. + version = current_update.contents.version; } // Update the cursor and scroll position if @@ -407,6 +420,7 @@ export const activate = (context: vscode.ExtensionContext) => { // viewport, but a bit below it. TextEditorRevealType.AtTop, ); + ignore_selection_change = false; } let cursor_line = current_update.cursor_position; @@ -424,8 +438,9 @@ export const activate = (context: vscode.ExtensionContext) => { cursor_position, ), ]; + ignore_selection_change = false; } - sendResult(id); + await sendResult(id); break; } @@ -440,10 +455,12 @@ export const activate = (context: vscode.ExtensionContext) => { current_file, ); } catch (e) { - sendResult( - id, - `Error: unable to open file ${current_file}: ${e}`, - ); + await sendResult(id, { + OpenFileFailed: [ + current_file, + (e as Error).toString(), + ], + }); continue; } ignore_active_editor_change = true; @@ -453,7 +470,7 @@ export const activate = (context: vscode.ExtensionContext) => { current_editor?.viewColumn, ); ignore_active_editor_change = false; - sendResult(id); + await sendResult(id); } else { // TODO: open using a custom document editor. // See @@ -475,15 +492,17 @@ export const activate = (context: vscode.ExtensionContext) => { }, ) .then( - () => sendResult(id), - (reason) => - sendResult( - id, - `Error: unable to open file ${current_file}: ${reason}`, - ), + async () => await sendResult(id), + async (reason) => + await sendResult(id, { + OpenFileFailed: [ + current_file, + reason, + ], + }), ); } - sendResult(id); + await sendResult(id); } break; } @@ -504,12 +523,17 @@ export const activate = (context: vscode.ExtensionContext) => { // Look through all open documents to see if we have // the requested file. const doc = get_document(load_file); - const load_file_result = - doc === undefined ? null : doc.getText(); + const load_file_result: null | [string, number] = + doc === undefined + ? null + : [ + doc.getText(), + (version = Math.random()), + ]; console_log( `CodeChat Editor extension: Result(LoadFile(${format_struct(load_file_result)}))`, ); - codeChatEditorServer.sendResultLoadfile( + await codeChatEditorServer.sendResultLoadfile( id, load_file_result, ); @@ -520,7 +544,7 @@ export const activate = (context: vscode.ExtensionContext) => { const client_html = value as string; assert(webview_panel !== undefined); webview_panel.webview.html = client_html; - sendResult(id); + await sendResult(id); // Now that the Client is loaded, send the editor's // current file to the server. send_update(false); @@ -555,19 +579,26 @@ export const deactivate = async () => { // Format a complex data structure as a string when in debug mode. const format_struct = (complex_data_structure: any): string => DEBUG_ENABLED - ? JSON.stringify(complex_data_structure).substring( - 0, - MAX_MESSAGE_LENGTH, - ) + ? JSON.stringify( + // If the struct is `undefined`, print an empty string. + complex_data_structure ?? "", + ).substring(0, MAX_MESSAGE_LENGTH) : ""; // Send a result (a response to a message from the server) back to the server. -const sendResult = (id: number, result: string | null = null) => { +const sendResult = async (id: number, result?: ResultErrTypes) => { assert(codeChatEditorServer); console_log( `CodeChat Editor extension: sending Result(id = ${id}, ${format_struct(result)}).`, ); - codeChatEditorServer.sendResult(id, result); + try { + await codeChatEditorServer.sendResult( + id, + result === undefined ? undefined : JSON.stringify(result), + ); + } catch (e) { + show_error(`Error in sendResult for id ${id}: ${e}.`); + } }; // This is called after an event such as an edit, when the CodeChat panel @@ -580,11 +611,11 @@ const send_update = (this_is_dirty: boolean) => { if (idle_timer !== undefined) { clearTimeout(idle_timer); } - // ... schedule a render after 300 ms. + // ... schedule a render after an autosave timeout. idle_timer = setTimeout(async () => { if (can_render()) { - const ate = vscode.window.activeTextEditor!; - if (ate !== current_editor) { + const ate = vscode.window.activeTextEditor; + if (ate !== undefined && ate !== current_editor) { // Send a new current file after a short delay; this allows // the user to rapidly cycle through several editors without // needing to reload the Client with each cycle. @@ -593,9 +624,13 @@ const send_update = (this_is_dirty: boolean) => { console_log( `CodeChat Editor extension: sending CurrentFile(${current_file}}).`, ); - await codeChatEditorServer!.sendMessageCurrentFile( - current_file, - ); + try { + await codeChatEditorServer!.sendMessageCurrentFile( + current_file, + ); + } catch (e) { + show_error(`Error sending CurrentFile message: ${e}.`); + } // Since we just requested a new file, the contents are // clean by definition. is_dirty = false; @@ -610,12 +645,14 @@ const send_update = (this_is_dirty: boolean) => { // CodeMirror // [Text.line](https://codemirror.net/docs/ref/#state.Text.line) // is 1-based. - const cursor_position = ate.selection.active.line + 1; - const scroll_position = ate.visibleRanges[0].start.line + 1; - const file_path = ate.document.fileName; + const cursor_position = + current_editor!.selection.active.line + 1; + const scroll_position = + current_editor!.visibleRanges[0].start.line + 1; + const file_path = current_editor!.document.fileName; // Send contents only if necessary. - const option_contents = is_dirty - ? ate.document.getText() + const option_contents: null | [string, number] = is_dirty + ? [current_editor!.document.getText(), (version = rand())] : null; is_dirty = false; console_log( @@ -628,7 +665,7 @@ const send_update = (this_is_dirty: boolean) => { scroll_position, ); } - }, 300); + }, autosave_timeout_ms); } }; @@ -680,8 +717,10 @@ const show_error = (message: string) => { // client, and the webview is visible. const can_render = () => { return ( - vscode.window.activeTextEditor !== undefined && + (vscode.window.activeTextEditor !== undefined || + current_editor !== undefined) && codeChatEditorServer !== undefined && + // TODO: I don't think these matter -- the Server is in charge of sending output to the Client. (codechat_client_location === CodeChatEditorClientLocation.browser || webview_panel !== undefined) ); diff --git a/extensions/VSCode/src/lib.rs b/extensions/VSCode/src/lib.rs index c837e6d6..a0108ddf 100644 --- a/extensions/VSCode/src/lib.rs +++ b/extensions/VSCode/src/lib.rs @@ -37,7 +37,11 @@ use code_chat_editor::{ide, webserver}; pub fn init_server(extension_base_path: String) -> Result<(), Error> { webserver::init_server( Some(&PathBuf::from(extension_base_path)), - LevelFilter::Debug, + if cfg!(debug_assertions) { + LevelFilter::Debug + } else { + LevelFilter::Warn + }, ) .map_err(|err| Error::new(Status::GenericFailure, err.to_string())) } @@ -86,7 +90,7 @@ impl CodeChatEditorServer { &self, file_path: String, // `null` to send no source code; a string to send the source code. - option_contents: Option, + option_contents: Option<(String, f64)>, cursor_position: Option, scroll_position: Option, ) -> std::io::Result { @@ -99,16 +103,25 @@ impl CodeChatEditorServer { pub async fn send_result( &self, id: f64, + // If provided, a JSON-encoded `ResultErrTypes`. message_result: Option, ) -> std::io::Result<()> { - self.0.send_result(id, message_result).await + let message = if let Some(err_json) = message_result { + match serde_json::from_str(&err_json) { + Ok(v) => Some(v), + Err(err) => return Err(std::io::Error::other(err.to_string())), + } + } else { + None + }; + self.0.send_result(id, message).await } #[napi] pub async fn send_result_loadfile( &self, id: f64, - load_file: Option, + load_file: Option<(String, f64)>, ) -> std::io::Result<()> { self.0.send_result_loadfile(id, load_file).await } diff --git a/server/Cargo.lock b/server/Cargo.lock index 78c10c6c..5cfd343b 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-files" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0d87f10d70e2948ad40e8edea79c8e77c6c66e0250a4c1f09b690465199576" +checksum = "4009a8beb4dc78a58286ac9d58969ee0a8acecb7912d5ce898b4da4335579341" dependencies = [ "actix-http", "actix-service", @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.12.0" +version = "4.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2233f53f6cb18ae038ce1f0713ca0c72ca0c4b71fe9aaeb59924ce2c89c6dd85" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" dependencies = [ "actix-codec", "actix-http", @@ -206,7 +206,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -467,7 +467,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.46" +version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "jobserver", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -716,9 +716,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -735,7 +735,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -756,6 +756,7 @@ dependencies = [ "actix-web", "actix-web-httpauth", "actix-ws", + "anyhow", "assert_cmd", "assert_fs", "assertables", @@ -847,6 +848,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -897,9 +907,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -1022,27 +1032,29 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.110", + "rustc_version", + "syn 2.0.111", "unicode-xid", ] @@ -1104,7 +1116,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1122,7 +1134,7 @@ dependencies = [ "anyhow", "bumpalo", "hashbrown 0.15.5", - "indexmap 2.12.0", + "indexmap 2.12.1", "rustc-hash", "serde", "unicode-width 0.2.2", @@ -1400,7 +1412,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1512,7 +1524,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.12.0", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -1538,9 +1550,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1565,8 +1577,9 @@ dependencies = [ [[package]] name = "htmd" -version = "0.3.2" -source = "git+https://github.com/bjones1/htmd.git?branch=fix-faithful-serialization#e13d5a5ed195163c60b0c59974fc5bb521bdd70b" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60ae59466542f2346e43d4a5e9b4432a1fc915b279c9fc0484e9ed7379121454" dependencies = [ "html5ever", "markup5ever_rcdom", @@ -1597,12 +1610,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1613,7 +1625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -1624,7 +1636,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body", "pin-project-lite", ] @@ -1663,7 +1675,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body", "httparse", "itoa", @@ -1680,7 +1692,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", + "http 1.4.0", "hyper", "hyper-util", "rustls", @@ -1693,16 +1705,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body", "hyper", "ipnet", @@ -1885,12 +1897,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -2000,7 +2012,7 @@ checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2037,9 +2049,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -2085,9 +2097,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" @@ -2102,9 +2114,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" +checksum = "8b484ba8d4f775eeca644c452a56650e544bf7e617f1d170fe7298122ead5222" dependencies = [ "zlib-rs", ] @@ -2149,11 +2161,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2254,7 +2266,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2517,9 +2529,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", "ucd-trie", @@ -2527,9 +2539,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -2537,22 +2549,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "pest_meta" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ "pest", "sha2", @@ -2618,7 +2630,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3005,7 +3017,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -3060,6 +3072,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -3089,9 +3110,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" dependencies = [ "web-time", "zeroize", @@ -3152,7 +3173,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3189,6 +3210,12 @@ dependencies = [ "zip", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -3238,7 +3265,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3247,7 +3274,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "itoa", "memchr", "ryu", @@ -3263,7 +3290,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3293,7 +3320,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "itoa", "ryu", "serde", @@ -3347,9 +3374,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -3499,9 +3526,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -3525,7 +3552,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3590,8 +3617,8 @@ dependencies = [ "cfg-if", "const_format", "futures-util", - "http 1.3.1", - "indexmap 2.12.0", + "http 1.4.0", + "indexmap 2.12.1", "libc", "pastey", "reqwest", @@ -3614,7 +3641,7 @@ source = "git+https://github.com/bjones1/thirtyfour.git?branch=selenium_manager# dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3643,7 +3670,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3654,7 +3681,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3748,7 +3775,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3818,7 +3845,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "serde_core", "serde_spanned", "toml_datetime", @@ -3868,14 +3895,14 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -3898,9 +3925,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -3910,20 +3937,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", ] @@ -3952,7 +3979,7 @@ checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "termcolor", ] @@ -3964,7 +3991,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.3.1", + "http 1.4.0", "httparse", "log", "rand 0.9.2", @@ -4110,9 +4137,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "js-sys", "wasm-bindgen", @@ -4181,9 +4208,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -4194,9 +4221,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -4207,9 +4234,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4217,31 +4244,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -4388,7 +4415,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4399,7 +4426,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4675,9 +4702,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" [[package]] name = "winsafe" @@ -4776,28 +4803,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4817,7 +4844,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", "synstructure", ] @@ -4838,7 +4865,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4871,7 +4898,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -4883,16 +4910,16 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.12.0", + "indexmap 2.12.1", "memchr", "zopfli", ] [[package]] name = "zlib-rs" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7" [[package]] name = "zopfli" diff --git a/server/Cargo.toml b/server/Cargo.toml index 8e9dd85c..0792f084 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -61,6 +61,7 @@ actix-service = "2.0.3" 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. @@ -74,7 +75,7 @@ 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 = "fix-faithful-serialization", version = "0.3.2" } +htmd = { version = "0.5" } imara-diff = { version = "0.2", features = [] } indoc = "2.0.5" lazy_static = "1" diff --git a/server/src/ide.rs b/server/src/ide.rs index 2fc1b56e..6a7119ae 100644 --- a/server/src/ide.rs +++ b/server/src/ide.rs @@ -51,7 +51,8 @@ use crate::{ translation::{CreatedTranslationQueues, create_translation_queues}, webserver::{ self, EditorMessage, EditorMessageContents, INITIAL_IDE_MESSAGE_ID, MESSAGE_ID_INCREMENT, - REPLY_TIMEOUT_MS, ResultOkTypes, UpdateMessageContents, WebAppState, setup_server, + REPLY_TIMEOUT_MS, ResultErrTypes, ResultOkTypes, UpdateMessageContents, WebAppState, + setup_server, }, }; @@ -160,7 +161,7 @@ impl CodeChatEditorServer { Some( EditorMessage { id, - message: EditorMessageContents::Result(Err(format!("Timeout: message id {id} unacknowledged."))) + message: EditorMessageContents::Result(Err(webserver::ResultErrTypes::MessageTimeout(id))) } ), else => None, @@ -250,7 +251,7 @@ impl CodeChatEditorServer { &self, file_path: String, // `null` to send no source code; a string to send the source code. - option_contents: Option, + option_contents: Option<(String, f64)>, cursor_position: Option, scroll_position: Option, ) -> std::io::Result { @@ -261,9 +262,10 @@ impl CodeChatEditorServer { mode: "".to_string(), }, source: CodeMirrorDiffable::Plain(CodeMirror { - doc: contents, + doc: contents.0, doc_blocks: vec![], }), + version: contents.1, }), cursor_position, scroll_position: scroll_position.map(|x| x as f32), @@ -271,11 +273,11 @@ impl CodeChatEditorServer { .await } - // Send either an Ok(Void) or an Error result to the Client. + /// Send either an Ok(Void) or an Error result to the Client. pub async fn send_result( &self, id: f64, - message_result: Option, + message_result: Option, ) -> std::io::Result<()> { let editor_message = EditorMessage { id, @@ -293,7 +295,7 @@ impl CodeChatEditorServer { pub async fn send_result_loadfile( &self, id: f64, - load_file: Option, + load_file: Option<(String, f64)>, ) -> std::io::Result<()> { self.send_message_raw(EditorMessage { id, diff --git a/server/src/ide/filewatcher.rs b/server/src/ide/filewatcher.rs index 159c3f16..7bc05741 100644 --- a/server/src/ide/filewatcher.rs +++ b/server/src/ide/filewatcher.rs @@ -40,6 +40,7 @@ use notify_debouncer_full::{ DebounceEventResult, new_debouncer, notify::{EventKind, RecursiveMode}, }; +use rand::random; use regex::Regex; use tokio::{ fs::DirEntry, @@ -57,7 +58,7 @@ use crate::{ processing::CodeMirrorDiffable, queue_send, webserver::{ - INITIAL_IDE_MESSAGE_ID, MESSAGE_ID_INCREMENT, ResultOkTypes, WebAppState, + INITIAL_IDE_MESSAGE_ID, MESSAGE_ID_INCREMENT, ResultErrTypes, ResultOkTypes, WebAppState, filesystem_endpoint, get_test_mode, }, }; @@ -463,12 +464,12 @@ async fn processing_task( for err in err_vec { // Report errors locally and to the CodeChat // Editor. - let msg = format!("Watcher error: {err}"); - error!("{msg}"); + let err = ResultErrTypes::FileWatchingError(err.to_string()); + error!("{err:?}"); // Send using an ID which indicates this isn't a // response to a message received from the // client. - send_response(&from_ide_tx, RESERVED_MESSAGE_ID, Err(msg)).await; + send_response(&from_ide_tx, RESERVED_MESSAGE_ID, Err(err)).await; } } @@ -534,7 +535,9 @@ async fn processing_task( source: crate::processing::CodeMirrorDiffable::Plain(CodeMirror { doc: file_contents, doc_blocks: vec![], - }) + }), + // The filewatcher doesn't store a version, since it only accepts plain (non-diff) results. Provide a version so the Client stays in sync with any diffs. Produce a whole number to avoid encoding difference with fractional values. + version: random::() as f64, }), cursor_position: None, scroll_position: None, @@ -556,10 +559,7 @@ async fn processing_task( // file. If `canonicalize` fails, then the files // don't match. if Some(Path::new(&update_message_contents.file_path).to_path_buf()) != current_filepath { - break 'process Err(format!( - "Update for file '{}' doesn't match current file '{current_filepath:?}'.", - update_message_contents.file_path - )); + break 'process Err(ResultErrTypes::WrongFileUpdate(update_message_contents.file_path, current_filepath.clone())); } // With code or a path, there's nothing to save. let codechat_for_web = match update_message_contents.contents { @@ -578,26 +578,14 @@ async fn processing_task( // it, in order to avoid a watch notification // from this write. if let Err(err) = debounced_watcher.unwatch(cfp) { - let msg = format!( - "Unable to unwatch file '{}': {err}.", - cfp.to_string_lossy() - ); - break 'process Err(msg); + break 'process Err(ResultErrTypes::FileUnwatchError(cfp.to_path_buf(), err.to_string())); } // Save this string to a file. if let Err(err) = fs::write(cfp.as_path(), plain.doc).await { - let msg = format!( - "Unable to save file '{}': {err}.", - cfp.to_string_lossy() - ); - break 'process Err(msg); + break 'process Err(ResultErrTypes::SaveFileError(cfp.to_path_buf(), err.to_string())); } if let Err(err) = debounced_watcher.watch(cfp, RecursiveMode::NonRecursive) { - let msg = format!( - "Unable to watch file '{}': {err}.", - cfp.to_string_lossy() - ); - break 'process Err(msg); + break 'process Err(ResultErrTypes::FileWatchError(cfp.to_path_buf(), err.to_string())); } Ok(ResultOkTypes::Void) }; @@ -611,19 +599,14 @@ async fn processing_task( if let Some(cfp) = ¤t_filepath && let Err(err) = debounced_watcher.unwatch(cfp) { - break 'err_exit Err(format!( - "Unable to unwatch file '{}': {err}.", - cfp.to_string_lossy() - )); + break 'err_exit Err(ResultErrTypes::FileUnwatchError(cfp.to_path_buf(), err.to_string())); } // Update to the new path. current_filepath = Some(file_path.to_path_buf()); // Watch the new file. - if let Err(err) = debounced_watcher.watch(file_path, RecursiveMode::NonRecursive) { - break 'err_exit Err(format!( - "Unable to watch file '{file_path_str}': {err}.", - )); + if let Err(err) = debounced_watcher.watch(&file_path, RecursiveMode::NonRecursive) { + break 'err_exit Err(ResultErrTypes::FileWatchError(file_path.to_path_buf(), err.to_string())); } // Indicate there was no error in the `Result` // message. @@ -656,9 +639,9 @@ async fn processing_task( EditorMessageContents::OpenUrl(_) | EditorMessageContents::ClientHtml(_) | EditorMessageContents::RequestClose => { - let msg = format!("Client sent unsupported message type {m:?}"); - error!("{msg}"); - send_response(&from_ide_tx, m.id, Err(msg)).await; + let err = ResultErrTypes::ClientIllegalMessage; + error!("{err:?}"); + send_response(&from_ide_tx, m.id, Err(err)).await; } } } @@ -727,7 +710,6 @@ mod tests { dev::{Service, ServiceResponse}, test, }; - use assertables::assert_starts_with; use dunce::simplified; use path_slash::PathExt; use pretty_assertions::assert_eq; @@ -745,8 +727,8 @@ mod tests { webserver::{ EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, INITIAL_IDE_MESSAGE_ID, INITIAL_MESSAGE_ID, IdeType, MESSAGE_ID_INCREMENT, - ResultOkTypes, UpdateMessageContents, WebAppState, WebsocketQueues, configure_app, - drop_leading_slash, make_app_data, send_response, set_root_path, + ResultErrTypes, ResultOkTypes, UpdateMessageContents, WebAppState, WebsocketQueues, + configure_app, drop_leading_slash, make_app_data, send_response, set_root_path, }, }; @@ -866,8 +848,14 @@ mod tests { send_response(&from_client_tx, id, Ok(ResultOkTypes::Void)).await; // Check the contents. - let translation_results = source_to_codechat_for_web("", &"py".to_string(), false, false); - let codechat_for_web = cast!(translation_results, TranslationResults::CodeChat); + let translation_results = source_to_codechat_for_web( + "", + &"py".to_string(), + umc.contents.as_ref().unwrap().version, + false, + false, + ); + let codechat_for_web = cast!(cast!(translation_results, Ok), TranslationResults::CodeChat); assert_eq!(umc.contents, Some(codechat_for_web)); // Report any errors produced when removing the temporary directory. @@ -963,7 +951,7 @@ mod tests { .unwrap(); let (id_rx, msg_rx) = get_message_as!(to_client_rx, EditorMessageContents::Result); assert_eq!(id, id_rx); - assert_starts_with!(cast!(&msg_rx, Err), "Client must not send this message."); + matches!(cast!(&msg_rx, Err), ResultErrTypes::ClientIllegalMessage); } // 5. Send an update message with no path. @@ -982,6 +970,7 @@ mod tests { doc: "".to_string(), doc_blocks: vec![], }), + version: 0.0, }), cursor_position: None, scroll_position: None, @@ -993,10 +982,7 @@ mod tests { // Check that it produces an error. let (id, err_msg) = get_message_as!(to_client_rx, EditorMessageContents::Result); assert_eq!(id, INITIAL_CLIENT_MESSAGE_ID + 4.0 * MESSAGE_ID_INCREMENT); - assert_starts_with!( - cast!(err_msg, Err), - "Update for file '' doesn't match current file" - ); + cast!(cast!(err_msg, Err), ResultErrTypes::WrongFileUpdate, _a, _b); // 6. Send an update message with unknown source language. // @@ -1014,6 +1000,7 @@ mod tests { doc: "testing".to_string(), doc_blocks: vec![], }), + version: 1.0, }), cursor_position: None, scroll_position: None, @@ -1023,13 +1010,12 @@ mod tests { .unwrap(); // Check that it produces an error. + let (msg_id, msg) = get_message_as!(to_client_rx, EditorMessageContents::Result); assert_eq!( - get_message_as!(to_client_rx, EditorMessageContents::Result), - ( - INITIAL_CLIENT_MESSAGE_ID + 5.0 * MESSAGE_ID_INCREMENT, - Err("Unable to translate to source: Invalid mode".to_string()) - ) + msg_id, + INITIAL_CLIENT_MESSAGE_ID + 5.0 * MESSAGE_ID_INCREMENT ); + cast!(cast!(msg, Err), ResultErrTypes::CannotTranslateCodeChat); // 7. Send a valid message. // @@ -1047,6 +1033,7 @@ mod tests { doc: "testing()".to_string(), doc_blocks: vec![], }), + version: 2.0, }), cursor_position: None, scroll_position: None, @@ -1073,8 +1060,10 @@ mod tests { fs::write(&file_path, s).unwrap(); // Wait for the filewatcher to debounce this file write. sleep(Duration::from_secs(2)).await; + // The version is random; don't check it with a fixed value. + let msg = get_message_as!(to_client_rx, EditorMessageContents::Update); assert_eq!( - get_message_as!(to_client_rx, EditorMessageContents::Update), + msg, ( INITIAL_IDE_MESSAGE_ID + MESSAGE_ID_INCREMENT, UpdateMessageContents { @@ -1087,6 +1076,7 @@ mod tests { doc: "testing()123".to_string(), doc_blocks: vec![], }), + version: msg.1.contents.as_ref().unwrap().version, }), cursor_position: None, scroll_position: None, diff --git a/server/src/ide/vscode.rs b/server/src/ide/vscode.rs index 10f1591b..18b378ec 100644 --- a/server/src/ide/vscode.rs +++ b/server/src/ide/vscode.rs @@ -45,9 +45,9 @@ use crate::{ translation_task, }, webserver::{ - EditorMessage, EditorMessageContents, IdeType, RESERVED_MESSAGE_ID, ResultOkTypes, - WebAppState, client_websocket, escape_html, filesystem_endpoint, get_client_framework, - get_server_url, html_wrapper, send_response, + EditorMessage, EditorMessageContents, IdeType, RESERVED_MESSAGE_ID, ResultErrTypes, + ResultOkTypes, WebAppState, client_websocket, escape_html, filesystem_endpoint, + get_client_framework, get_server_url, html_wrapper, send_response, }, }; @@ -125,9 +125,10 @@ pub fn vscode_ide_core( // Make sure it's the `Opened` message. let EditorMessageContents::Opened(ide_type) = first_message.message else { - let msg = format!("Unexpected message {first_message:?}"); - error!("{msg}"); - send_response(&to_ide_tx, first_message.id, Err(msg)).await; + let id = first_message.id; + let err = ResultErrTypes::UnexpectedMessage(format!("{:#?}", first_message)); + error!("{err}"); + send_response(&to_ide_tx, id, Err(err)).await; // Send a `Closed` message to shut down the websocket. queue_send!(to_ide_tx.send(EditorMessage { id: RESERVED_MESSAGE_ID, message: EditorMessageContents::Closed}), 'task); @@ -182,7 +183,7 @@ pub fn vscode_ide_core( // Make sure it's the `Result` message with no errors. let res = // First, make sure the ID matches. - if message.id != 0.0 { + if message.id != RESERVED_MESSAGE_ID { Err(format!("Unexpected message ID {}.", message.id)) } else { match message.message { @@ -214,9 +215,9 @@ pub fn vscode_ide_core( if let Err(err) = webbrowser::open(&format!("{address}/vsc/cf/{connection_id_raw}")) { - let msg = format!("Unable to open web browser: {err}"); - error!("{msg}"); - send_response(&to_ide_tx, first_message.id, Err(msg)).await; + let err = ResultErrTypes::WebBrowserOpenFailed(err.to_string()); + error!("{err:?}"); + send_response(&to_ide_tx, first_message.id, Err(err)).await; // Send a `Closed` message. queue_send!(to_ide_tx.send(EditorMessage{ @@ -231,9 +232,9 @@ pub fn vscode_ide_core( } _ => { // This is the wrong IDE type. Report the error. - let msg = format!("Invalid IDE type: {ide_type:?}"); - error!("{msg}"); - send_response(&to_ide_tx, first_message.id, Err(msg)).await; + let err = ResultErrTypes::InvalidIdeType(ide_type); + error!("{err:?}"); + send_response(&to_ide_tx, first_message.id, Err(err)).await; // Close the connection. queue_send!(to_ide_tx.send(EditorMessage { id: RESERVED_MESSAGE_ID, message: EditorMessageContents::Closed}), 'task); diff --git a/server/src/ide/vscode/tests.rs b/server/src/ide/vscode/tests.rs index fd3e109b..63178391 100644 --- a/server/src/ide/vscode/tests.rs +++ b/server/src/ide/vscode/tests.rs @@ -49,7 +49,7 @@ use tokio_tungstenite::{ use crate::webserver::{ EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, INITIAL_IDE_MESSAGE_ID, - INITIAL_MESSAGE_ID, IdeType, MESSAGE_ID_INCREMENT, + INITIAL_MESSAGE_ID, IdeType, MESSAGE_ID_INCREMENT, ResultErrTypes, }; use crate::{ cast, @@ -268,7 +268,7 @@ async fn test_vscode_ide_websocket1() { let em = read_message(&mut ws_ide).await; let result = cast!(em.message, EditorMessageContents::Result); - assert_starts_with!(cast!(&result, Err), "Unexpected message"); + matches!(cast!(&result, Err), ResultErrTypes::ClientIllegalMessage); // Next, expect the websocket to be closed. let err = &ws_ide.next().await.unwrap().unwrap(); @@ -516,9 +516,10 @@ async fn test_vscode_ide_websocket8() { &mut ws_ide, &EditorMessage { id: INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT, - message: EditorMessageContents::Result(Ok(ResultOkTypes::LoadFile(Some( + message: EditorMessageContents::Result(Ok(ResultOkTypes::LoadFile(Some(( "# testing".to_string(), - )))), + 0.0, + ))))), }, ) .await; @@ -547,6 +548,7 @@ async fn test_vscode_ide_websocket8() { contents: "

testing

\n".to_string() }], }), + version: 0.0, }), cursor_position: None, scroll_position: None, @@ -648,6 +650,7 @@ async fn test_vscode_ide_websocket7() { doc: "# more".to_string(), doc_blocks: vec![], }), + version: 0.0, }), cursor_position: None, scroll_position: None, @@ -674,7 +677,8 @@ async fn test_vscode_ide_websocket7() { delimiter: "#".to_string(), contents: "

more

\n".to_string() }] - }) + }), + version: 0.0, }), cursor_position: None, scroll_position: None, @@ -720,6 +724,7 @@ async fn test_vscode_ide_websocket7() { .to_string(), doc_blocks: vec![], }), + version: 1.0, }), cursor_position: None, scroll_position: None, @@ -749,8 +754,10 @@ async fn test_vscode_ide_websocket7() { indent: "".to_string(), delimiter: "#".to_string(), contents: "

most

\n".to_string() - })] + })], + version: 0.0, }), + version: 1.0, }), cursor_position: None, scroll_position: None, @@ -807,6 +814,7 @@ async fn test_vscode_ide_websocket6() { contents: "less\n".to_string(), }], }), + version: 0.0, }), cursor_position: None, scroll_position: None, @@ -828,6 +836,7 @@ async fn test_vscode_ide_websocket6() { doc: "# less\n".to_string(), doc_blocks: vec![], }), + version: 0.0, }), cursor_position: None, scroll_position: None, @@ -946,8 +955,11 @@ async fn test_vscode_ide_websocket4() { // This should also produce an `Update` message sent from the Server. // // Message ids: IDE - 0, Server - 2->3, Client - 0. + // + // Since the version is randomly generated, copy that from the received message. + let msg = read_message(&mut ws_client).await; assert_eq!( - read_message(&mut ws_client).await, + msg, EditorMessage { id: INITIAL_MESSAGE_ID + 2.0 * MESSAGE_ID_INCREMENT, message: EditorMessageContents::Update(UpdateMessageContents { @@ -966,6 +978,11 @@ async fn test_vscode_ide_websocket4() { contents: "

test.py

\n".to_string() }], }), + version: cast!(&msg.message, EditorMessageContents::Update) + .contents + .as_ref() + .unwrap() + .version, }), cursor_position: None, scroll_position: None, @@ -1025,6 +1042,8 @@ async fn test_vscode_ide_websocket4() { .await; join_handle.join().unwrap(); + // What makes sense here? If the IDE didn't load the file, either the Client shouldn't edit it or the Client should switch to using a filewatcher for edits. + /* // Send an update from the Client, which should produce a diff. // // Message ids: IDE - 0, Server - 4, Client - 0->1. @@ -1048,6 +1067,7 @@ async fn test_vscode_ide_websocket4() { contents: "

test.py

".to_string(), }], }), + version: 1.0, }), cursor_position: None, scroll_position: None, @@ -1072,7 +1092,9 @@ async fn test_vscode_ide_websocket4() { insert: format!("More{}", if cfg!(windows) { "\r\n" } else { "\n" }), }], doc_blocks: vec![], + version: 0.0, }), + version: 1.0, }), cursor_position: None, scroll_position: None, @@ -1094,6 +1116,7 @@ async fn test_vscode_ide_websocket4() { message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) } ); + */ check_logger_errors(0); // Report any errors produced when removing the temporary directory. diff --git a/server/src/lexer/supported_languages.rs b/server/src/lexer/supported_languages.rs index f8450a9a..3949a6b1 100644 --- a/server/src/lexer/supported_languages.rs +++ b/server/src/lexer/supported_languages.rs @@ -56,6 +56,8 @@ use super::{ StringDelimiterSpec, pest_parser, }; +pub const MARKDOWN_MODE: &str = "markdown"; + // Helper functions // ----------------------------------------------------------------------------- // @@ -543,7 +545,7 @@ pub fn get_language_lexer_vec() -> Vec { ), // ### Markdown make_language_lexer( - "markdown", + MARKDOWN_MODE, &["md"], &[], &[], diff --git a/server/src/processing.rs b/server/src/processing.rs index 48423d6b..1bd0b633 100644 --- a/server/src/processing.rs +++ b/server/src/processing.rs @@ -59,7 +59,10 @@ use serde::{Deserialize, Serialize}; use ts_rs::TS; // ### Local -use crate::lexer::{CodeDocBlock, DocBlock, LEXERS, LanguageLexerCompiled, source_lexer}; +use crate::lexer::{ + CodeDocBlock, DocBlock, LEXERS, LanguageLexerCompiled, source_lexer, + supported_languages::MARKDOWN_MODE, +}; // Data structures // ----------------------------------------------------------------------------- @@ -84,6 +87,8 @@ use crate::lexer::{CodeDocBlock, DocBlock, LEXERS, LanguageLexerCompiled, source pub struct CodeChatForWeb { pub metadata: SourceFileMetadata, pub source: CodeMirrorDiffable, + /// The version number after accepting this update. + pub version: f64, } /// Provide two options for sending CodeMirror data -- as the full contents @@ -123,6 +128,8 @@ pub struct CodeMirrorDiff { /// A diff of the document being edited. pub doc: Vec, pub doc_blocks: Vec, + /// The version number from which this diff was produced. + pub version: f64, } /// A transaction produced by the diff of the `CodeMirror` struct. @@ -187,11 +194,10 @@ pub struct CodeMirrorDocBlockDelete { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, TS)] #[ts(export, optional_fields)] pub struct StringDiff { - /// The index into the previous `CodeMirrorDocBlockVec` of the start of the - /// change. + /// The index of the start of the change, in UTF-16 code units. pub from: usize, /// The index of the end of the change; defined for deletions and - /// replacements. See the + /// replacements, in UTF-16 code units. See the /// [skip serializing field docs](https://serde.rs/attr-skip-serializing.html); /// this must be excluded from the JSON output if it's `None` to avoid /// CodeMirror errors. @@ -208,9 +214,6 @@ pub enum TranslationResults { /// This file is unknown to and therefore not supported by the CodeChat /// Editor. Unknown, - /// This is a CodeChat Editor file but it contains errors that prevent its - /// translation. The string contains the error message. - Err(String), /// A CodeChat Editor file; the struct contains the file's contents /// translated to CodeMirror. CodeChat(CodeChatForWeb), @@ -225,9 +228,6 @@ pub enum TranslationResultsString { /// This file is unknown to the CodeChat Editor. It must be viewed raw or /// using the simple viewer. Unknown, - /// This is a CodeChat Editor file but it contains errors that prevent its - /// translation. The string contains the error message. - Err(String), /// A CodeChat Editor file; the struct contains the file's contents /// translated to CodeMirror. CodeChat(CodeChatForWeb), @@ -389,6 +389,20 @@ pub fn find_path_to_toc(file_path: &Path) -> Option { } } +#[derive(Debug, thiserror::Error)] +pub enum CodechatForWebToSourceError { + #[error("invalid lexer {0}")] + InvalidLexer(String), + #[error("doc blocks not allowed in Markdown documents")] + DocBlocksNotAllowed, + #[error("TODO: diffs not supported")] + TodoDiff, + #[error("unable to convert from HTML to Markdown: {0}")] + HtmlToMarkdownFailed(#[from] HtmlToMarkdownWrappedError), + #[error("unable to translate CodeChat to source: {0}")] + CannotTranslateCodeChat(#[from] CodeDocBlockVecToSourceError), +} + // Transform `CodeChatForWeb` to source code // ----------------------------------------------------------------------------- /// This function takes in a source file in web-editable format (the @@ -396,37 +410,41 @@ pub fn find_path_to_toc(file_path: &Path) -> Option { pub fn codechat_for_web_to_source( // The file to save plus metadata, stored in the `LexedSourceFile` codechat_for_web: &CodeChatForWeb, -) -> Result { +) -> Result { + let lexer_name = &codechat_for_web.metadata.mode; // Given the mode, find the lexer. - let lexer: &std::sync::Arc = match LEXERS - .map_mode_to_lexer - .get(&codechat_for_web.metadata.mode) - { - Some(v) => v, - None => return Err("Invalid mode".to_string()), - }; + let lexer: &std::sync::Arc = + match LEXERS.map_mode_to_lexer.get(lexer_name) { + Some(v) => v, + None => { + return Err(CodechatForWebToSourceError::InvalidLexer( + lexer_name.clone(), + )); + } + }; // Extract the plain (not diffed) CodeMirror contents. let CodeMirrorDiffable::Plain(ref code_mirror) = codechat_for_web.source else { - panic!("No diff!"); + return Err(CodechatForWebToSourceError::TodoDiff); }; // If this is a Markdown-only document, handle this special case. - if *lexer.language_lexer.lexer_name == "markdown" { + if *lexer.language_lexer.lexer_name == MARKDOWN_MODE { // There should be no doc blocks. if !code_mirror.doc_blocks.is_empty() { - return Err("Doc blocks not allowed in Markdown documents.".to_string()); + return Err(CodechatForWebToSourceError::DocBlocksNotAllowed); } // Translate the HTML document to Markdown. let converter = HtmlToMarkdownWrapped::new(); return converter .convert(&code_mirror.doc) - .map_err(|e| e.to_string()); + .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed); } let code_doc_block_vec_html = code_mirror_to_code_doc_blocks(code_mirror); - let code_doc_block_vec = - doc_block_html_to_markdown(code_doc_block_vec_html).map_err(|e| e.to_string())?; + let code_doc_block_vec = doc_block_html_to_markdown(code_doc_block_vec_html) + .map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed)?; code_doc_block_vec_to_source(&code_doc_block_vec, lexer) + .map_err(CodechatForWebToSourceError::CannotTranslateCodeChat) } /// Return the byte index of `s[u16_16_index]`, where the indexing operation is @@ -505,6 +523,14 @@ struct HtmlToMarkdownWrapped { word_wrap_config: Configuration, } +#[derive(Debug, thiserror::Error)] +pub enum HtmlToMarkdownWrappedError { + #[error("unable to convert from HTML to markdown")] + HtmlToMarkdownFailed(#[from] std::io::Error), + #[error("unable to word wrap Markdown")] + WordWrapFailed(#[from] anyhow::Error), +} + impl HtmlToMarkdownWrapped { fn new() -> Self { HtmlToMarkdownWrapped { @@ -532,11 +558,10 @@ impl HtmlToMarkdownWrapped { self.word_wrap_config.line_width = line_width as u32; } - fn convert(&self, html: &str) -> std::io::Result { + fn convert(&self, html: &str) -> Result { let converted = self.html_to_markdown.convert(html)?; Ok( - format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None)) - .map_err(std::io::Error::other)? + 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/)). @@ -549,7 +574,7 @@ impl HtmlToMarkdownWrapped { // Transform HTML in doc blocks to Markdown. fn doc_block_html_to_markdown( mut code_doc_block_vec: Vec, -) -> std::io::Result> { +) -> Result, HtmlToMarkdownWrappedError> { let mut converter = HtmlToMarkdownWrapped::new(); for code_doc_block in &mut code_doc_block_vec { if let CodeDocBlock::DocBlock(doc_block) = code_doc_block { @@ -567,20 +592,24 @@ fn doc_block_html_to_markdown( WORD_WRAP_COLUMN, ), )); - doc_block.contents = converter - .convert(&doc_block.contents) - .map_err(std::io::Error::other)?; + doc_block.contents = converter.convert(&doc_block.contents)?; } } Ok(code_doc_block_vec) } +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum CodeDocBlockVecToSourceError { + #[error("unknown comment opening delimiter '{0}'")] + UnknownCommentOpeningDelimiter(String), +} + // Turn this vec of CodeDocBlocks into a string of source code. fn code_doc_block_vec_to_source( code_doc_block_vec: &Vec, lexer: &LanguageLexerCompiled, -) -> Result { +) -> Result { let mut file_contents = String::new(); for code_doc_block in code_doc_block_vec { match code_doc_block { @@ -637,10 +666,11 @@ fn code_doc_block_vec_to_source( { Some(index) => &lexer.language_lexer.block_comment_delim_arr[index].closing, None => { - return Err(format!( - "Unknown comment opening delimiter '{}'.", - doc_block.delimiter - )); + return Err( + CodeDocBlockVecToSourceError::UnknownCommentOpeningDelimiter( + doc_block.delimiter.clone(), + ), + ); } }; @@ -723,6 +753,12 @@ fn code_doc_block_vec_to_source( Ok(file_contents) } +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum SourceToCodeChatForWebError { + #[error("unknown lexer {0}")] + UnknownLexer(String), +} + // Transform from source code to `CodeChatForWeb` // ----------------------------------------------------------------------------- // @@ -733,11 +769,13 @@ pub fn source_to_codechat_for_web( file_contents: &str, // The file's extension. file_ext: &String, + // The version of this file. + version: f64, // True if this file is a TOC. _is_toc: bool, // True if this file is part of a project. _is_project: bool, -) -> TranslationResults { +) -> Result { // Determine the lexer to use for this file. let lexer_name; // First, search for a lexer directive in the file contents. @@ -746,10 +784,7 @@ pub fn source_to_codechat_for_web( match LEXERS.map_mode_to_lexer.get(&lexer_name) { Some(v) => v, None => { - return TranslationResults::Err(format!( - "

Unknown lexer type {}.

", - &lexer_name - )); + return Err(SourceToCodeChatForWebError::UnknownLexer(lexer_name)); } } } else { @@ -758,7 +793,7 @@ pub fn source_to_codechat_for_web( Some(llc) => llc.first().unwrap(), _ => { // The file type is unknown; treat it as plain text. - return TranslationResults::Unknown; + return Ok(TranslationResults::Unknown); } } }; @@ -769,7 +804,8 @@ pub fn source_to_codechat_for_web( metadata: SourceFileMetadata { mode: lexer.language_lexer.lexer_name.to_string(), }, - source: if lexer.language_lexer.lexer_name.as_str() == "markdown" { + version, + source: if lexer.language_lexer.lexer_name.as_str() == MARKDOWN_MODE { // Document-only files are easy: just encode the contents. let html = markdown_to_html(file_contents); // TODO: process the HTML. @@ -862,7 +898,7 @@ pub fn source_to_codechat_for_web( }, }; - TranslationResults::CodeChat(codechat_for_web) + Ok(TranslationResults::CodeChat(codechat_for_web)) } // Like `source_to_codechat_for_web`, translate a source file to the CodeChat @@ -874,14 +910,19 @@ pub fn source_to_codechat_for_web_string( file_contents: &str, // The path to this file. file_path: &Path, + // The version to assign to this file. + version: f64, // True if this file is a TOC. is_toc: bool, -) -> ( - // The resulting translation. - TranslationResultsString, - // Path to the TOC, if found; otherwise, None. - Option, -) { +) -> Result< + ( + // The resulting translation. + TranslationResultsString, + // Path to the TOC, if found; otherwise, None. + Option, + ), + SourceToCodeChatForWebError, +> { // Determine the file's extension, in order to look up a lexer. let ext = &file_path .extension() @@ -894,26 +935,34 @@ pub fn source_to_codechat_for_web_string( let path_to_toc = find_path_to_toc(file_path); let is_project = path_to_toc.is_some(); - ( - match source_to_codechat_for_web(file_contents, &ext.to_string(), is_toc, is_project) { - TranslationResults::CodeChat(codechat_for_web) => { - if is_toc { - // For the table of contents sidebar, which is pure - // markdown, just return the resulting HTML, rather than the - // editable CodeChat for web format. - let CodeMirrorDiffable::Plain(plain) = codechat_for_web.source else { - panic!("No diff!"); - }; - TranslationResultsString::Toc(plain.doc) - } else { - TranslationResultsString::CodeChat(codechat_for_web) + Ok(( + match source_to_codechat_for_web( + file_contents, + &ext.to_string(), + version, + is_toc, + is_project, + ) { + Err(err) => return Err(err), + Ok(translation_results) => match translation_results { + TranslationResults::CodeChat(codechat_for_web) => { + if is_toc { + // For the table of contents sidebar, which is pure + // markdown, just return the resulting HTML, rather than the + // editable CodeChat for web format. + let CodeMirrorDiffable::Plain(plain) = codechat_for_web.source else { + panic!("No diff!"); + }; + TranslationResultsString::Toc(plain.doc) + } else { + TranslationResultsString::CodeChat(codechat_for_web) + } } - } - TranslationResults::Unknown => TranslationResultsString::Unknown, - TranslationResults::Err(err) => TranslationResultsString::Err(err), + TranslationResults::Unknown => TranslationResultsString::Unknown, + }, }, path_to_toc, - ) + )) } /// Convert markdown to HTML. (This assumes the Markdown defined in the @@ -933,11 +982,11 @@ fn markdown_to_html(markdown: &str) -> String { // ### Diff support // // This section provides methods to diff the previous and current -// `CodeMirrorDocBlockVec`.Β  The primary purpose is to fix a visual bug: if the +// `CodeMirrorDocBlockVec`. The primary purpose is to fix a visual bug: if the // entire CodeMirror data structure is overwritten, then CodeMirror loses track // of the correct vertical scroll bar position, probably because it has build up // information on the size of each rendered doc block; these correct sizes are -// reset when all data is overrwritten, causing unexpected scrolling. Therefore, +// reset when all data is overwritten, causing unexpected scrolling. Therefore, // this approach is to modify only what changed, rather than changing // everything. As a secondary goal, this hopefully improves overall performance // by sending less data between the server and the client, in spite of the @@ -967,11 +1016,11 @@ fn markdown_to_html(markdown: &str) -> String { // 1. Use the diff algorithm to find the minimal change set between a before and // after `CodeMirrorDocBlocksVec`, which only looks at the `contents`. This // avoids "noise" from changes in from/to fields from obscuring changes only -// to theΒ `contents`. -// 2. For all before and after blocks whoseΒ `contents` were identical, compare +// to the `contents`. +// 2. For all before and after blocks whose `contents` were identical, compare // the other fields, adding these to the change set, but not attempting to // use the diff algorithm. -// 3. Represent changes to theΒ `contents` as a `StringDiff`. +// 3. Represent changes to the `contents` as a `StringDiff`. // // #### String diff /// Given two strings, return a list of changes between them. diff --git a/server/src/processing/tests.rs b/server/src/processing/tests.rs index 8e19a7eb..c5f9c0c2 100644 --- a/server/src/processing/tests.rs +++ b/server/src/processing/tests.rs @@ -33,11 +33,16 @@ use super::{ TranslationResults, find_path_to_toc, }; use crate::{ - lexer::{CodeDocBlock, DocBlock, compile_lexers, supported_languages::get_language_lexer_vec}, + cast, + lexer::{ + CodeDocBlock, DocBlock, compile_lexers, + supported_languages::{MARKDOWN_MODE, get_language_lexer_vec}, + }, prep_test_dir, processing::{ - CodeMirrorDiffable, CodeMirrorDocBlockDelete, CodeMirrorDocBlockTransaction, - CodeMirrorDocBlockUpdate, byte_index_of, code_doc_block_vec_to_source, + CodeDocBlockVecToSourceError, CodeMirrorDiffable, CodeMirrorDocBlockDelete, + CodeMirrorDocBlockTransaction, CodeMirrorDocBlockUpdate, CodechatForWebToSourceError, + SourceToCodeChatForWebError, byte_index_of, code_doc_block_vec_to_source, code_mirror_to_code_doc_blocks, codechat_for_web_to_source, diff_code_mirror_doc_blocks, diff_str, source_to_codechat_for_web, }, @@ -60,6 +65,7 @@ fn build_codechat_for_web( doc: doc.to_string(), doc_blocks, }), + version: 0.0, } } @@ -110,14 +116,14 @@ fn run_test(mode: &str, doc: &str, doc_blocks: Vec) -> VecUnknown lexer type unknown.

".to_string()) + Err(SourceToCodeChatForWebError::UnknownLexer( + "unknown".to_string() + )) ); // A CodeChat Editor document via filename. assert_eq!( - source_to_codechat_for_web("", &"md".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web("markdown", "", vec![])) + source_to_codechat_for_web("", &"md".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( + MARKDOWN_MODE, + "", + vec![] + ))) ); // A CodeChat Editor document via lexer specification. @@ -514,40 +524,49 @@ fn test_source_to_codechat_for_web_1() { source_to_codechat_for_web( &format!("{lexer_spec}markdown"), &"xxx".to_string(), + 0.0, false, false, ), - TranslationResults::CodeChat(build_codechat_for_web( - "markdown", + Ok(TranslationResults::CodeChat(build_codechat_for_web( + MARKDOWN_MODE, &format!("

{lexer_spec}markdown

\n"), vec![] - )) + ))) ); // An empty source file. assert_eq!( - source_to_codechat_for_web("", &"js".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web("javascript", "", vec![])) + source_to_codechat_for_web("", &"js".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( + "javascript", + "", + vec![] + ))) ); // A zero doc block source file. assert_eq!( - source_to_codechat_for_web("let a = 1;", &"js".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web("javascript", "let a = 1;", vec![])) + source_to_codechat_for_web("let a = 1;", &"js".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( + "javascript", + "let a = 1;", + vec![] + ))) ); // One doc block source files. assert_eq!( - source_to_codechat_for_web("// Test", &"js".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("// Test", &"js".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "javascript", "\n", vec![build_codemirror_doc_block(0, 1, "", "//", "

Test

\n")] - )) + ))) ); assert_eq!( - source_to_codechat_for_web("let a = 1;\n// Test", &"js".to_string(), false, false,), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("let a = 1;\n// Test", &"js".to_string(), 0.0, false, false,), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "javascript", "let a = 1;\n\n", vec![build_codemirror_doc_block( @@ -557,15 +576,15 @@ fn test_source_to_codechat_for_web_1() { "//", "

Test

\n" )] - )) + ))) ); assert_eq!( - source_to_codechat_for_web("// Test\nlet a = 1;", &"js".to_string(), false, false,), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("// Test\nlet a = 1;", &"js".to_string(), 0.0, false, false,), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "javascript", "\nlet a = 1;", vec![build_codemirror_doc_block(0, 1, "", "//", "

Test

\n")] - )) + ))) ); // A two doc block source file. This also tests references in one block to a @@ -574,10 +593,11 @@ fn test_source_to_codechat_for_web_1() { source_to_codechat_for_web( "// [Link][1]\nlet a = 1;\n/* [1]: http://b.org */", &"js".to_string(), + 0.0, false, false, ), - TranslationResults::CodeChat(build_codechat_for_web( + Ok(TranslationResults::CodeChat(build_codechat_for_web( "javascript", "\nlet a = 1;\n\n", vec![ @@ -590,7 +610,7 @@ fn test_source_to_codechat_for_web_1() { ), build_codemirror_doc_block(12, 13, "", "/*", "") ] - )) + ))) ); // Trigger special cases: @@ -599,8 +619,8 @@ fn test_source_to_codechat_for_web_1() { // * A doc block in the middle of the file // * A doc block with no trailing newline at the end of the file. assert_eq!( - source_to_codechat_for_web("//\n\n//\n\n//", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("//\n\n//\n\n//", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\n\n\n\n", vec![ @@ -608,11 +628,11 @@ fn test_source_to_codechat_for_web_1() { build_codemirror_doc_block(2, 3, "", "//", ""), build_codemirror_doc_block(4, 5, "", "//", "") ] - )) + ))) ); assert_eq!( - source_to_codechat_for_web("// ~~~\n\n//\n\n//", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("// ~~~\n\n//\n\n//", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\n\n\n\n", vec![ @@ -620,34 +640,40 @@ fn test_source_to_codechat_for_web_1() { build_codemirror_doc_block(2, 3, "", "//", ""), build_codemirror_doc_block(4, 5, "", "//", "") ] - )) + ))) ); // Test Unicode characters in code. assert_eq!( - source_to_codechat_for_web("; // Οƒ\n//", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("; // Οƒ\n//", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "; // Οƒ\n", vec![build_codemirror_doc_block(7, 8, "", "//", ""),] - )) + ))) ); // Test Unicode characters in strings. assert_eq!( - source_to_codechat_for_web("\"Οƒ\";\n//", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("\"Οƒ\";\n//", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\"Οƒ\";\n", vec![build_codemirror_doc_block(5, 6, "", "//", ""),] - )) + ))) ); // Test a fenced code block that's unterminated. See [fence // mending](#fence-mending). assert_eq!( - source_to_codechat_for_web("/* ``` foo\n*/\n// Test", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web( + "/* ``` foo\n*/\n// Test", + &"cpp".to_string(), + 0.0, + false, + false + ), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\n\n\n", vec![ @@ -660,17 +686,18 @@ fn test_source_to_codechat_for_web_1() { ), build_codemirror_doc_block(2, 3, "", "//", "

Test

\n"), ] - )) + ))) ); // Test the other code fence character (the tilde). assert_eq!( source_to_codechat_for_web( "/* ~~~~~~~ foo\n*/\n// Test", &"cpp".to_string(), + 0.0, false, false ), - TranslationResults::CodeChat(build_codechat_for_web( + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\n\n\n", vec![ @@ -683,46 +710,52 @@ fn test_source_to_codechat_for_web_1() { ), build_codemirror_doc_block(2, 3, "", "//", "

Test

\n"), ] - )) + ))) ); // Test multiple unterminated fenced code blocks. assert_eq!( - source_to_codechat_for_web("// ```\n // ~~~", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("// ```\n // ~~~", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "
\n
\n"), build_codemirror_doc_block(1, 2, " ", "//", "
\n"), ] - )) + ))) ); // Test an unterminated HTML block. assert_eq!( - source_to_codechat_for_web("// \n // Test", &"cpp".to_string(), false, false), - TranslationResults::CodeChat(build_codechat_for_web( + source_to_codechat_for_web("// \n // Test", &"cpp".to_string(), 0.0, false, false), + Ok(TranslationResults::CodeChat(build_codechat_for_web( "c_cpp", "\n\n", vec![ build_codemirror_doc_block(0, 1, "", "//", "\n"), build_codemirror_doc_block(1, 2, " ", "//", "

Test

\n"), ] - )) + ))) ); // Test an unterminated `
` block. Ensure that markdown after this is
     // still parsed.
     assert_eq!(
-        source_to_codechat_for_web("// 
\n // *Test*", &"cpp".to_string(), false, false),
-        TranslationResults::CodeChat(build_codechat_for_web(
+        source_to_codechat_for_web(
+            "// 
\n // *Test*",
+            &"cpp".to_string(),
+            0.0,
+            false,
+            false
+        ),
+        Ok(TranslationResults::CodeChat(build_codechat_for_web(
             "c_cpp",
             "\n\n",
             vec![
                 build_codemirror_doc_block(0, 1, "", "//", "
\n\n"),
                 build_codemirror_doc_block(1, 2, " ", "//", "

Test

\n"), ] - )) + ))) ); } diff --git a/server/src/translation.rs b/server/src/translation.rs index c947a113..4fa64f59 100644 --- a/server/src/translation.rs +++ b/server/src/translation.rs @@ -209,26 +209,27 @@ use std::{collections::HashMap, ffi::OsStr, fmt::Debug, path::PathBuf}; // ### Third-party use lazy_static::lazy_static; use log::{debug, error, warn}; +use rand::random; use regex::Regex; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::{fs::File, select, sync::mpsc}; +use crate::lexer::supported_languages::MARKDOWN_MODE; +use crate::processing::CodeMirrorDocBlockVec; // ### Local -use crate::webserver::{ - EditorMessage, EditorMessageContents, WebAppState, WebsocketQueues, send_response, -}; use crate::{ - oneshot_send, processing::{ - CodeChatForWeb, CodeMirror, CodeMirrorDiff, CodeMirrorDiffable, SourceFileMetadata, - TranslationResultsString, codechat_for_web_to_source, diff_code_mirror_doc_blocks, - diff_str, source_to_codechat_for_web_string, + CodeChatForWeb, CodeMirror, CodeMirrorDiff, CodeMirrorDiffable, CodeMirrorDocBlock, + SourceFileMetadata, TranslationResultsString, codechat_for_web_to_source, + diff_code_mirror_doc_blocks, diff_str, source_to_codechat_for_web_string, }, - queue_send, + queue_send, queue_send_func, webserver::{ - INITIAL_MESSAGE_ID, MESSAGE_ID_INCREMENT, ProcessingTaskHttpRequest, ResultOkTypes, - SimpleHttpResponse, SimpleHttpResponseError, SyncState, UpdateMessageContents, - file_to_response, path_to_url, try_canonicalize, try_read_as_text, url_to_path, + EditorMessage, EditorMessageContents, INITIAL_MESSAGE_ID, MESSAGE_ID_INCREMENT, + ProcessingTaskHttpRequest, ResultErrTypes, ResultOkTypes, SimpleHttpResponse, + SimpleHttpResponseError, UpdateMessageContents, WebAppState, WebsocketQueues, + file_to_response, path_to_url, send_response, try_canonicalize, try_read_as_text, + url_to_path, }, }; @@ -236,10 +237,10 @@ use crate::{ // ------- // // The max length of a message to show in the console. -const MAX_MESSAGE_LENGTH: usize = 300; +const MAX_MESSAGE_LENGTH: usize = 3000; lazy_static! { - /// A regex to determine the type of the first EOL. See 'PROCESSINGS1. + /// A regex to determine the type of the first EOL. See 'PROCESSINGS`. pub static ref EOL_FINDER: Regex = Regex::new("[^\r\n]*(\r?\n)").unwrap(); } @@ -297,12 +298,12 @@ pub fn create_translation_queues( ) -> Result { // There are three cases for this `connection_id`: // - // 1. It hasn't been used before. In this case, create the appropriate - // queues and start websocket and processing tasks. - // 2. It's in use, but was disconnected. In this case, re-use the queues - // and start the websocket task; the processing task is still running. - // 3. It's in use by another IDE. This is an error, but I don't have a way - // to detect it yet. + // 1. It hasn't been used before. In this case, create the appropriate + // queues and start websocket and processing tasks. + // 2. It's in use, but was disconnected. In this case, re-use the queues and + // start the websocket task; the processing task is still running. + // 3. It's in use by another IDE. This is an error, but I don't have a way + // to detect it yet. // // Check case 3. if app_state @@ -374,14 +375,68 @@ pub fn create_translation_queues( }) } -// This is the processing task for the Visual Studio Code IDE. It handles all -// the core logic to moving data between the IDE and the client. +/// This holds the state used by the main loop of the translation task; this allows factoring out lengthy contents in the loop into subfunctions. +struct TranslationTask { + // These parameters are passed to us. + connection_id_raw: String, + prefix: &'static [&'static str], + allow_source_diffs: bool, + to_ide_tx: Sender, + from_ide_rx: Receiver, + to_client_tx: Sender, + from_client_rx: Receiver, + from_http_rx: Receiver, + + // These parameters are internal state. + // + /// The file currently loaded in the Client. + current_file: PathBuf, + /// A map of `LoadFile` requests sent to the IDE, awaiting its response. + load_file_requests: HashMap, + /// The id for messages created by the server. Leave space for a server message during the init phase. + id: f64, + /// The source code, provided by the IDE. It will use whatever the + /// IDE provides for EOLs, which is stored in `eol` below. + source_code: String, + code_mirror_doc: String, + eol: EolType, + /// Some means this contains valid HTML; None means don't use it + /// (since it would have contained Markdown). + code_mirror_doc_blocks: Option>, + prefix_str: String, + /// To support sending diffs, we must provide a way to determine if + /// the sender and receiver have the same file contents before + /// applying a diff. File contents can become unsynced due to: + /// + /// 1. A dropped/lost message between the IDE and Client. + /// 2. Edits to file contents in two locations before updates from + /// one location (the Client, for example) propagate to the other + /// location (the IDE). + /// + /// Therefore, assign each file a version number. All files are sent + /// with a unique, randomly-generated version number which define the + /// file's version after this update is applied. Diffs also include + /// the version number of the file before applying the diff; the + // receiver's current version number must match with the sender's + /// pre-diff version number in order to apply the diff. When the + /// versions don't match, the IDE must send a full text file to the + /// Server and Client to re-sync. When a file is first loaded, its + /// version number is None, signaling that the sender must always + /// provide the full text, not a diff. + version: f64, + /// Has the full (non-diff) version of the current file been sent? + /// Don't send diffs until this is sent. + sent_full: bool, +} + +/// This is the processing task for the Visual Studio Code IDE. It handles all +/// the core logic to moving data between the IDE and the client. #[allow(clippy::too_many_arguments)] pub async fn translation_task( connection_id_prefix: String, connection_id_raw: String, prefix: &'static [&'static str], - app_state_task: WebAppState, + app_state: WebAppState, shutdown_only: bool, allow_source_diffs: bool, to_ide_tx: Sender, @@ -389,599 +444,684 @@ pub async fn translation_task( to_client_tx: Sender, mut from_client_rx: Receiver, ) { - // Start the processing task. let connection_id = format!("{connection_id_prefix}{connection_id_raw}"); if !shutdown_only { - // Use a [labeled block - // expression](https://doc.rust-lang.org/reference/expressions/loop-expr.html#labelled-block-expressions) - // to provide a way to exit the current task. - 'task: { - let mut current_file = PathBuf::new(); - let mut load_file_requests: HashMap = HashMap::new(); - debug!("VSCode processing task started."); - - // Create a queue for HTTP requests fo communicate with this task. - let (from_http_tx, mut from_http_rx) = mpsc::channel(10); - app_state_task - .processing_task_queue_tx - .lock() - .unwrap() - .insert(connection_id.to_string(), from_http_tx); - - // Leave space for a server message during the init phase. - let mut id: f64 = INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT; - // The source code, provided by the IDE. It will use whatever the - // IDE provides for EOLs, which is stored in `eol` below. - let mut source_code = String::new(); - let mut code_mirror_doc = String::new(); + debug!("VSCode processing task started."); + + // Create a queue for HTTP requests fo communicate with this task. + let (from_http_tx, from_http_rx) = mpsc::channel(10); + app_state + .processing_task_queue_tx + .lock() + .unwrap() + .insert(connection_id.to_string(), from_http_tx); + + let mut continue_loop = true; + let mut tt = TranslationTask { + connection_id_raw, + prefix, + allow_source_diffs, + to_ide_tx, + from_ide_rx, + to_client_tx, + from_client_rx, + from_http_rx, + current_file: PathBuf::new(), + load_file_requests: HashMap::new(), + id: INITIAL_MESSAGE_ID + MESSAGE_ID_INCREMENT, + source_code: String::new(), + code_mirror_doc: String::new(), // The initial state will be overwritten by the first `Update` or // `LoadFile`, so this value doesn't matter. - let mut eol = EolType::Lf; + eol: EolType::Lf, // Some means this contains valid HTML; None means don't use it // (since it would have contained Markdown). - let mut code_mirror_doc_blocks = Some(Vec::new()); - let prefix_str = "/".to_string() + &prefix.join("/"); - // To send a diff from Server to Client or vice versa, we need to - // ensure they are in sync: - // - // 1. IDE update -> Server -> Client or Client update -> Server -> - // IDE: the Server and Client sync is pending. Client response - // -> Server -> IDE or IDE response -> Server -> Client: the - // Server and Client are synced. - // 2. IDE current file -> Server -> Client or Client current file - // -> Server -> IDE: Out of sync. - // - // It's only safe to send a diff when the most recent sync is - // achieved. So, we need to track the ID of the most recent IDE -> - // Client update or Client -> IDE update, if one is in flight. When - // complete, mark the connection as synchronized. Since all IDs are - // unique, we can use a single variable to store the ID. - // - // Currently, when the Client sends an update, mark the connection - // as out of sync, since the update contains not HTML in the doc - // blocks, but Markdown. When Turndown is moved from JavaScript to - // Rust, this can be changed, since both sides will have HTML in the - // doc blocks. - // - // Another approach: use revision numbers. Both the IDE and Client - // start with the same revision number. When either makes an edit, - // it sends a new revision number along with a diff. If the receiver - // doesn't have the previous version, it returns a result of error, - // which prompts the sender to re-send with the full text instead of - // a diff. - let mut sync_state = SyncState::OutOfSync; - loop { - select! { - // Look for messages from the IDE. - Some(ide_message) = from_ide_rx.recv() => { - debug!("Received IDE message id = {}, message = {}", ide_message.id, debug_shorten(&ide_message.message)); - match ide_message.message { - // Handle messages that the IDE must not send. - EditorMessageContents::Opened(_) | - EditorMessageContents::OpenUrl(_) | - EditorMessageContents::LoadFile(_) | - EditorMessageContents::ClientHtml(_) => { - let msg = "IDE must not send this message."; - error!("{msg}"); - send_response(&to_ide_tx, ide_message.id, Err(msg.to_string())).await; - }, - - // Handle messages that are simply passed through. - EditorMessageContents::Closed | - EditorMessageContents::RequestClose => { - debug!("Forwarding it to the Client."); - queue_send!(to_client_tx.send(ide_message)) - }, - - // Pass a `Result` message to the Client, unless - // it's a `LoadFile` result. - EditorMessageContents::Result(ref result) => { - let is_loadfile = match result { - // See if this error was produced by a - // `LoadFile` result. - Err(_) => load_file_requests.contains_key(&ide_message.id.to_bits()), - Ok(result_ok) => match result_ok { - ResultOkTypes::Void => false, - ResultOkTypes::LoadFile(_) => true, - } - }; - // Pass the message to the client if this isn't - // a `LoadFile` result (the only type of result - // which the Server should handle). - if !is_loadfile { - debug!("Forwarding it to the Client."); - // If this was confirmation from the IDE - // that it received the latest update, then - // mark the IDE as synced. - if sync_state == SyncState::Pending(ide_message.id) { - sync_state = SyncState::InSync; - } - queue_send!(to_client_tx.send(ide_message)); - continue; - } - // Ensure there's an HTTP request for this - // `LoadFile` result. - let Some(http_request) = load_file_requests.remove(&ide_message.id.to_bits()) else { - error!("Error: no HTTP request found for LoadFile result ID {}.", ide_message.id); - break 'task; - }; - - // Take ownership of the result after sending it - // above (which requires ownership). - let EditorMessageContents::Result(result) = ide_message.message else { - error!("{}", "Not a result."); - break; - }; - // Get the file contents from a `LoadFile` - // result; otherwise, this is None. - let file_contents_option = match result { - Err(err) => { - error!("{err}"); - None - }, - Ok(result_ok) => match result_ok { - ResultOkTypes::Void => panic!("LoadFile result should not be void."), - ResultOkTypes::LoadFile(file_contents) => file_contents, - } - }; - - // Process the file contents. Since VSCode - // doesn't have a PDF viewer, determine if this - // is a PDF file. (TODO: look at the magic - // number also -- "%PDF"). - let use_pdf_js = http_request.file_path.extension() == Some(OsStr::new("pdf")); - let ((simple_http_response, option_update), file_contents) = match file_contents_option { - Some(file_contents) => { - // If there are Windows newlines, replace - // with Unix; this is reversed when the - // file is sent back to the IDE. - (file_to_response(&http_request, ¤t_file, Some(&file_contents), use_pdf_js).await, file_contents) - }, - None => { - // The file wasn't available in the IDE. - // Look for it in the filesystem. - match File::open(&http_request.file_path).await { - Err(err) => ( - ( - SimpleHttpResponse::Err(SimpleHttpResponseError::Io(err)), - None, - ), - // There's no file, so return empty - // contents, which will be ignored. - "".to_string() - ), - Ok(mut fc) => { - let option_file_contents = try_read_as_text(&mut fc).await; - ( - file_to_response( - &http_request, - ¤t_file, - option_file_contents.as_ref(), - use_pdf_js, - ) - .await, - // If the file is binary, return empty - // contents, which will be ignored. - option_file_contents.unwrap_or("".to_string()) - ) - } - } - } - }; - if let Some(update) = option_update { - let Some(ref tmp) = update.contents else { - error!("None."); - break; - }; - let CodeMirrorDiffable::Plain(ref plain) = tmp.source else { - error!("Not plain!"); - break; - }; - source_code = file_contents; - eol = find_eol_type(&source_code); - // We must clone here, since the original is - // placed in the TX queue. - code_mirror_doc = plain.doc.clone(); - code_mirror_doc_blocks = Some(plain.doc_blocks.clone()); - sync_state = SyncState::Pending(id); - - debug!("Sending Update to Client, id = {id}."); - queue_send!(to_client_tx.send(EditorMessage { - id, - message: EditorMessageContents::Update(update) + code_mirror_doc_blocks: Some(Vec::new()), + prefix_str: "/".to_string() + &prefix.join("/"), + version: 0.0, + // Don't send diffs until this is sent. + sent_full: false, + }; + while continue_loop { + select! { + // Look for messages from the IDE. + Some(ide_message) = tt.from_ide_rx.recv() => { + debug!("Received IDE message id = {}, message = {}", ide_message.id, debug_shorten(&ide_message.message)); + match ide_message.message { + // Handle messages that the IDE must not send. + EditorMessageContents::Opened(_) | + EditorMessageContents::OpenUrl(_) | + EditorMessageContents::LoadFile(_) | + EditorMessageContents::ClientHtml(_) => { + let err = ResultErrTypes::IdeIllegalMessage; + error!("{err:?}"); + send_response(&tt.to_ide_tx, ide_message.id, Err(err)).await; + }, + + // Handle messages that are simply passed through. + EditorMessageContents::Closed | + EditorMessageContents::RequestClose => { + debug!("Forwarding it to the Client."); + queue_send!(tt.to_client_tx.send(ide_message)) + }, + + EditorMessageContents::Result(_) => continue_loop = tt.ide_result(ide_message).await, + EditorMessageContents::Update(_) => continue_loop = tt.ide_update(ide_message).await, + + // Update the current file; translate it to a URL + // then pass it to the Client. + EditorMessageContents::CurrentFile(file_path, _is_text) => { + debug!("Translating and forwarding it to the Client."); + match try_canonicalize(&file_path) { + Ok(clean_file_path) => { + queue_send!(tt.to_client_tx.send(EditorMessage { + id: ide_message.id, + message: EditorMessageContents::CurrentFile( + path_to_url(&tt.prefix_str, Some(&tt.connection_id_raw), &clean_file_path), Some(true) + ) })); - id += MESSAGE_ID_INCREMENT; + tt.current_file = file_path.into(); + // Since this is a new file, mark it as + // unsent in full. + tt.sent_full = false; + } + Err(err) => { + error!("{err:?}"); + send_response(&tt.to_client_tx, ide_message.id, Err(ResultErrTypes::TryCanonicalizeError(err.to_string()))).await; } - debug!("Sending HTTP response."); - oneshot_send!(http_request.response_queue.send(simple_http_response)); } + } + } + }, - // Handle the `Update` message. - EditorMessageContents::Update(update) => { - // Normalize the provided file name. - let result = match try_canonicalize(&update.file_path) { - Err(err) => Err(err), - Ok(clean_file_path) => { - match update.contents { - None => { - queue_send!(to_client_tx.send(EditorMessage { - id: ide_message.id, - message: EditorMessageContents::Update(UpdateMessageContents { - file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), - contents: None, - cursor_position: update.cursor_position, - scroll_position: update.scroll_position, - }), - })); - Ok(ResultOkTypes::Void) - } + // Handle HTTP requests. + Some(http_request) = tt.from_http_rx.recv() => { + debug!("Received HTTP request for {:?} and sending LoadFile to IDE, id = {}.", http_request.file_path, tt.id); + // Convert the request into a `LoadFile` message. + queue_send!(tt.to_ide_tx.send(EditorMessage { + id: tt.id, + message: EditorMessageContents::LoadFile(http_request.file_path.clone()) + })); + // Store the ID and request, which are needed to send a + // response when the `LoadFile` result is received. + tt.load_file_requests.insert(tt.id.to_bits(), http_request); + tt.id += MESSAGE_ID_INCREMENT; + } - Some(contents) => { - match contents.source { - CodeMirrorDiffable::Diff(_diff) => Err("TODO: support for updates with diffable sources.".to_string()), - CodeMirrorDiffable::Plain(code_mirror) => { - // If there are Windows newlines, replace - // with Unix; this is reversed when the - // file is sent back to the IDE. - eol = find_eol_type(&code_mirror.doc); - let doc_normalized_eols = code_mirror.doc.replace("\r\n", "\n"); - // Translate the file. - let (translation_results_string, _path_to_toc) = - source_to_codechat_for_web_string(&doc_normalized_eols, ¤t_file, false); - match translation_results_string { - TranslationResultsString::CodeChat(ccfw) => { - // Send the new translated contents. - debug!("Sending translated contents to Client."); - let CodeMirrorDiffable::Plain(ref ccfw_source_plain) = ccfw.source else { - error!("{}", "Unexpected diff value."); - break; - }; - // Send a diff if possible (only when the - // Client's contents are synced with the - // IDE). - let contents = Some( - if let Some(cmdb) = code_mirror_doc_blocks && - sync_state == SyncState::InSync { - let doc_diff = diff_str(&code_mirror_doc, &ccfw_source_plain.doc); - let code_mirror_diff = diff_code_mirror_doc_blocks(&cmdb, &ccfw_source_plain.doc_blocks); - CodeChatForWeb { - // Clone needed here, so we can copy it - // later. - metadata: ccfw.metadata.clone(), - source: CodeMirrorDiffable::Diff(CodeMirrorDiff { - doc: doc_diff, - doc_blocks: code_mirror_diff - }) - } - } else { - // We must make a clone to put in the TX - // queue; this allows us to keep the - // original below to use with the next - // diff. - ccfw.clone() - } - ); - queue_send!(to_client_tx.send(EditorMessage { - id: ide_message.id, - message: EditorMessageContents::Update(UpdateMessageContents { - file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), - contents, - cursor_position: update.cursor_position, - scroll_position: update.scroll_position, - }), - })); - // Update to the latest code after - // computing diffs. To avoid ownership - // problems, re-define `ccfw_source_plain`. - let CodeMirrorDiffable::Plain(ccfw_source_plain) = ccfw.source else { - error!("{}", "Unexpected diff value."); - break; - }; - source_code = code_mirror.doc; - code_mirror_doc = ccfw_source_plain.doc; - code_mirror_doc_blocks = Some(ccfw_source_plain.doc_blocks); - // Mark the Client as unsynced until this - // is acknowledged. - sync_state = SyncState::Pending(ide_message.id); - Ok(ResultOkTypes::Void) - } - // TODO - TranslationResultsString::Binary => Err("TODO".to_string()), - TranslationResultsString::Err(err) => Err(format!("Error translating source to CodeChat: {err}").to_string()), - TranslationResultsString::Unknown => { - // Send the new raw contents. - debug!("Sending translated contents to Client."); - queue_send!(to_client_tx.send(EditorMessage { - id: ide_message.id, - message: EditorMessageContents::Update(UpdateMessageContents { - file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), - contents: Some(CodeChatForWeb { - metadata: SourceFileMetadata { - // Since this is raw data, `mode` doesn't - // matter. - mode: "".to_string(), - }, - source: CodeMirrorDiffable::Plain(CodeMirror { - doc: code_mirror.doc, - doc_blocks: vec![] - }) - }), - cursor_position: update.cursor_position, - scroll_position: update.scroll_position, - }), - })); - Ok(ResultOkTypes::Void) - }, - TranslationResultsString::Toc(_) => { - Err("Error: source incorrectly recognized as a TOC.".to_string()) - } - } - } - } - } - } - } - }; - // If there's an error, then report it; - // otherwise, the message is passed to the - // Client, which will provide the result. - if let Err(err) = &result { - error!("{err}"); - send_response(&to_ide_tx, ide_message.id, result).await; - } + // Handle messages from the client. + Some(client_message) = tt.from_client_rx.recv() => { + debug!("Received Client message id = {}, message = {}", client_message.id, debug_shorten(&client_message.message)); + match client_message.message { + // Handle messages that the client must not send. + EditorMessageContents::Opened(_) | + EditorMessageContents::LoadFile(_) | + EditorMessageContents::RequestClose | + EditorMessageContents::ClientHtml(_) => { + let err = ResultErrTypes::ClientIllegalMessage; + error!("{err:?}"); + send_response(&tt.to_client_tx, client_message.id, Err(err)).await; + }, + + // Handle messages that are simply passed through. + EditorMessageContents::Closed => { + debug!("Forwarding it to the IDE."); + queue_send!(tt.to_ide_tx.send(client_message)) + }, + + EditorMessageContents::Result(ref result) => { + debug!("Forwarding it to the IDE."); + // If the Client can't read our diff, send the full text next time. + if matches!(result, Err(ResultErrTypes::OutOfSync)) { + tt.sent_full = false; } + queue_send!(tt.to_ide_tx.send(client_message)) + }, - // Update the current file; translate it to a URL - // then pass it to the Client. - EditorMessageContents::CurrentFile(file_path, _is_text) => { - debug!("Translating and forwarding it to the Client."); - match try_canonicalize(&file_path) { - Ok(clean_file_path) => { - queue_send!(to_client_tx.send(EditorMessage { - id: ide_message.id, - message: EditorMessageContents::CurrentFile( - path_to_url(&prefix_str, Some(&connection_id_raw), &clean_file_path), Some(true) - ) - })); - current_file = file_path.into(); - // Since this is a new file, mark it as - // unsynced. - sync_state = SyncState::OutOfSync; - } - Err(err) => { - let msg = format!( - "Unable to canonicalize file name {}: {err}", &file_path - ); - error!("{msg}"); - send_response(&to_client_tx, ide_message.id, Err(msg)).await; + // Open a web browser when requested. + EditorMessageContents::OpenUrl(url) => { + // This doesn't work in Codespaces. TODO: send + // this back to the VSCode window, then call + // `vscode.env.openExternal(vscode.Uri.parse(url))`. + if let Err(err) = webbrowser::open(&url) { + let err = ResultErrTypes::WebBrowserOpenFailed(err.to_string()); + error!("{err:?}"); + send_response(&tt.to_client_tx, client_message.id, Err(err)).await; + } else { + send_response(&tt.to_client_tx, client_message.id, Ok(ResultOkTypes::Void)).await; + } + }, + + EditorMessageContents::Update(_) => continue_loop = tt.client_update(client_message).await, + + // Update the current file; translate it to a URL + // then pass it to the IDE. + EditorMessageContents::CurrentFile(url_string, _is_text) => { + debug!("Forwarding translated path to IDE."); + let result = match url_to_path(&url_string, tt.prefix) { + Err(err) => Err(ResultErrTypes::UrlToPathError(url_string.to_string(), err.to_string())), + Ok(file_path) => { + match file_path.to_str() { + None => Err(ResultErrTypes::NoPathToString(file_path)), + Some(file_path_string) => { + // Use a [binary file + // sniffer](#binary-file-sniffer) to + // determine if the file is text or binary. + let is_text = if let Ok(mut fc) = File::open(&file_path).await { + try_read_as_text(&mut fc).await.is_some() + } else { + false + }; + queue_send!(tt.to_ide_tx.send(EditorMessage { + id: client_message.id, + message: EditorMessageContents::CurrentFile(file_path_string.to_string(), Some(is_text)) + })); + tt.current_file = file_path; + // Since this is a new file, the full text hasn't been sent yet. + tt.sent_full = false; + Ok(()) + } } } + }; + if let Err(msg) = result { + error!("{msg}"); + send_response(&tt.to_client_tx, client_message.id, Err(msg)).await; } } - }, + } + }, - // Handle HTTP requests. - Some(http_request) = from_http_rx.recv() => { - debug!("Received HTTP request for {:?} and sending LoadFile to IDE, id = {id}.", http_request.file_path); - // Convert the request into a `LoadFile` message. - queue_send!(to_ide_tx.send(EditorMessage { - id, - message: EditorMessageContents::LoadFile(http_request.file_path.clone()) - })); - // Store the ID and request, which are needed to send a - // response when the `LoadFile` result is received. - load_file_requests.insert(id.to_bits(), http_request); - id += MESSAGE_ID_INCREMENT; + else => break + } + } + (from_ide_rx, from_client_rx) = (tt.from_ide_rx, tt.from_client_rx); + } + debug!("VSCode processing task shutting down."); + if app_state + .processing_task_queue_tx + .lock() + .unwrap() + .remove(&connection_id) + .is_none() + { + error!("Unable to remove connection ID {connection_id} from processing task queue."); + } + if app_state + .client_queues + .lock() + .unwrap() + .remove(&connection_id) + .is_none() + { + error!("Unable to remove connection ID {connection_id} from client queues."); + } + if app_state + .ide_queues + .lock() + .unwrap() + .remove(&connection_id) + .is_none() + { + error!("Unable to remove connection ID {connection_id} from IDE queues."); + } + + from_ide_rx.close(); + from_client_rx.close(); + + // Drain any remaining messages after closing the queue. + while let Some(m) = from_ide_rx.recv().await { + warn!("Dropped queued message {m:?}"); + } + while let Some(m) = from_client_rx.recv().await { + warn!("Dropped queued message {m:?}"); + } + debug!("VSCode processing task exited."); +} + +// These provide translation for messages passing through the Server. +impl TranslationTask { + // Pass a `Result` message to the Client, unless + // it's a `LoadFile` result. + async fn ide_result(&mut self, ide_message: EditorMessage) -> bool { + let EditorMessageContents::Result(ref result) = ide_message.message else { + panic!("Should only be called with a result."); + }; + let is_loadfile = match result { + // See if this error was produced by a + // `LoadFile` result. + Err(_) => self + .load_file_requests + .contains_key(&ide_message.id.to_bits()), + Ok(result_ok) => match result_ok { + ResultOkTypes::Void => false, + ResultOkTypes::LoadFile(_) => true, + }, + }; + // Pass the message to the client if this isn't + // a `LoadFile` result (the only type of result + // which the Server should handle). + if !is_loadfile { + debug!("Forwarding it to the Client."); + // If the Server can't read our diff, send the full text next time. + if matches!(result, Err(ResultErrTypes::OutOfSync)) { + self.sent_full = false; + } + queue_send_func!(self.to_client_tx.send(ide_message)); + return true; + } + // Ensure there's an HTTP request for this + // `LoadFile` result. + let Some(http_request) = self.load_file_requests.remove(&ide_message.id.to_bits()) else { + error!( + "Error: no HTTP request found for LoadFile result ID {}.", + ide_message.id + ); + return true; + }; + + // Take ownership of the result after sending it + // above (which requires ownership). + let EditorMessageContents::Result(result) = ide_message.message else { + panic!("Not a result."); + }; + // Get the file contents from a `LoadFile` + // result; otherwise, this is None. + let file_contents_option = match result { + Err(err) => { + error!("{err:?}"); + None + } + Ok(result_ok) => match result_ok { + ResultOkTypes::Void => panic!("LoadFile result should not be void."), + ResultOkTypes::LoadFile(file_contents) => file_contents, + }, + }; + + // Process the file contents. Since VSCode + // doesn't have a PDF viewer, determine if this + // is a PDF file. (TODO: look at the magic + // number also -- "%PDF"). + let use_pdf_js = http_request.file_path.extension() == Some(OsStr::new("pdf")); + let ((simple_http_response, option_update), file_contents) = match file_contents_option { + Some((file_contents, new_version)) => { + self.version = new_version; + // The IDE just sent the full contents; we're sending full contents to the Client. + self.sent_full = true; + ( + file_to_response( + &http_request, + new_version, + &self.current_file, + Some(&file_contents), + use_pdf_js, + ) + .await, + file_contents, + ) + } + None => { + // The file wasn't available in the IDE. + // Look for it in the filesystem. + match File::open(&http_request.file_path).await { + Err(err) => ( + ( + SimpleHttpResponse::Err(SimpleHttpResponseError::Io(err)), + None, + ), + // There's no file, so return empty + // contents, which will be ignored. + "".to_string(), + ), + Ok(mut fc) => { + let option_file_contents = try_read_as_text(&mut fc).await; + ( + file_to_response( + &http_request, + self.version, + &self.current_file, + option_file_contents.as_ref(), + use_pdf_js, + ) + .await, + // If the file is binary, return empty + // contents, which will be ignored. + option_file_contents.unwrap_or("".to_string()), + ) } + } + } + }; + if let Some(update) = option_update { + let Some(ref tmp) = update.contents else { + panic!("Contents must always be provided."); + }; + let CodeMirrorDiffable::Plain(ref plain) = tmp.source else { + panic!("Diff not supported."); + }; + self.source_code = file_contents; + self.eol = find_eol_type(&self.source_code); + // We must clone here, since the original is + // placed in the TX queue. + self.code_mirror_doc = plain.doc.clone(); + self.code_mirror_doc_blocks = Some(plain.doc_blocks.clone()); - // Handle messages from the client. - Some(client_message) = from_client_rx.recv() => { - debug!("Received Client message id = {}, message = {}", client_message.id, debug_shorten(&client_message.message)); - match client_message.message { - // Handle messages that the client must not send. - EditorMessageContents::Opened(_) | - EditorMessageContents::LoadFile(_) | - EditorMessageContents::RequestClose | - EditorMessageContents::ClientHtml(_) => { - let msg = "Client must not send this message."; - error!("{msg}"); - send_response(&to_client_tx, client_message.id, Err(msg.to_string())).await; - }, - - // Handle messages that are simply passed through. - EditorMessageContents::Closed | - EditorMessageContents::Result(_) => { - debug!("Forwarding it to the IDE."); - // If this result confirms that the Client - // received the most recent IDE update, then - // mark the documents as synced. - if sync_state == SyncState::Pending(client_message.id) { - sync_state = SyncState::InSync; - } - queue_send!(to_ide_tx.send(client_message)) - }, - - // Open a web browser when requested. - EditorMessageContents::OpenUrl(url) => { - // This doesn't work in Codespaces. TODO: send - // this back to the VSCode window, then call - // `vscode.env.openExternal(vscode.Uri.parse(url))`. - if let Err(err) = webbrowser::open(&url) { - let msg = format!("Unable to open web browser to URL {url}: {err}"); - error!("{msg}"); - send_response(&to_client_tx, client_message.id, Err(msg)).await; - } else { - send_response(&to_client_tx, client_message.id, Ok(ResultOkTypes::Void)).await; - } - }, + debug!("Sending Update to Client, id = {}.", self.id); + queue_send_func!(self.to_client_tx.send(EditorMessage { + id: self.id, + message: EditorMessageContents::Update(update) + })); + self.id += MESSAGE_ID_INCREMENT; + } + debug!("Sending HTTP response."); + if let Err(err) = http_request.response_queue.send(simple_http_response) { + error!("Unable to enqueue: {err:?}"); + return false; + } + + true + } + + async fn ide_update(&mut self, ide_message: EditorMessage) -> bool { + let EditorMessageContents::Update(update) = ide_message.message else { + panic!("Expected update message."); + }; + // Normalize the provided file name. + let result = match try_canonicalize(&update.file_path) { + Err(err) => Err(ResultErrTypes::TryCanonicalizeError(err.to_string())), + Ok(clean_file_path) => { + match update.contents { + None => { + queue_send_func!(self.to_client_tx.send(EditorMessage { + id: ide_message.id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), + contents: None, + cursor_position: update.cursor_position, + scroll_position: update.scroll_position, + }), + })); + Ok(ResultOkTypes::Void) + } - // Handle the `Update` message. - EditorMessageContents::Update(update_message_contents) => { - debug!("Forwarding translation of it to the IDE."); - match try_canonicalize(&update_message_contents.file_path) { + Some(contents) => { + match contents.source { + CodeMirrorDiffable::Diff(_diff) => Err(ResultErrTypes::TodoDiffSupport), + CodeMirrorDiffable::Plain(code_mirror) => { + // If there are Windows newlines, replace + // with Unix; this is reversed when the + // file is sent back to the IDE. + self.eol = find_eol_type(&code_mirror.doc); + let doc_normalized_eols = code_mirror.doc.replace("\r\n", "\n"); + // Translate the file. + match source_to_codechat_for_web_string( + &doc_normalized_eols, + &self.current_file, + contents.version, + false, + ) { Err(err) => { - let msg = format!( - "Unable to canonicalize file name {}: {err}", &update_message_contents.file_path - ); - error!("{msg}"); - send_response(&to_client_tx, client_message.id, Err(msg)).await; - continue; + Err(ResultErrTypes::CannotTranslateSource(err.to_string())) } - Ok(clean_file_path) => { - let codechat_for_web = match update_message_contents.contents { - None => None, - Some(cfw) => match codechat_for_web_to_source( - &cfw) - { - Ok(new_source_code) => { - // Correct EOL endings for use with the - // IDE. - let new_source_code_eol = eol_convert(new_source_code, &eol); - let ccfw = if sync_state == SyncState::InSync && allow_source_diffs { - Some(CodeChatForWeb { - metadata: cfw.metadata, - source: CodeMirrorDiffable::Diff(CodeMirrorDiff { - // Diff with correct EOLs, so that (for - // CRLF files as well as LF files) offsets - // are correct. - doc: diff_str(&source_code, &new_source_code_eol), - doc_blocks: vec![], - }), - }) - } else { - Some(CodeChatForWeb { - metadata: cfw.metadata, - source: CodeMirrorDiffable::Plain(CodeMirror { - // We must clone here, so that it can be - // placed in the TX queue. - doc: new_source_code_eol.clone(), - doc_blocks: vec![], - }), - }) - }; - source_code = new_source_code_eol; - let CodeMirrorDiffable::Plain(cmd) = cfw.source else { - // TODO: support diffable! - error!("No diff!"); - break; - }; - code_mirror_doc = cmd.doc; - // TODO: instead of `cmd.doc_blocks`, use - // `None` to indicate that the doc blocks - // contain Markdown instead of HTML. - code_mirror_doc_blocks = None; - ccfw - }, - Err(message) => { - let msg = format!( - "Unable to translate to source: {message}" - ); - error!("{msg}"); - send_response(&to_client_tx, client_message.id, Err(msg)).await; - continue; - } - }, - }; - queue_send!(to_ide_tx.send(EditorMessage { - id: client_message.id, - message: EditorMessageContents::Update(UpdateMessageContents { - file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), - contents: codechat_for_web, - cursor_position: update_message_contents.cursor_position, - scroll_position: update_message_contents.scroll_position, - }) - })); - // Mark the IDE contents as out of sync - // until this message is received. - sync_state = SyncState::Pending(client_message.id); - } - } - }, - - // Update the current file; translate it to a URL - // then pass it to the IDE. - EditorMessageContents::CurrentFile(url_string, _is_text) => { - debug!("Forwarding translated path to IDE."); - let result = match url_to_path(&url_string, prefix) { - Err(err) => Err(format!("Unable to convert URL to path: {err}")), - Ok(file_path) => { - match file_path.to_str() { - None => Err("Unable to convert path to string.".to_string()), - Some(file_path_string) => { - // Use a [binary file - // sniffer](#binary-file-sniffer) to - // determine if the file is text or binary. - let is_text = if let Ok(mut fc) = File::open(&file_path).await { - try_read_as_text(&mut fc).await.is_some() + Ok((translation_results_string, _path_to_toc)) => { + match translation_results_string { + TranslationResultsString::CodeChat(ccfw) => { + // Send the new translated contents. + debug!("Sending translated contents to Client."); + let CodeMirrorDiffable::Plain( + ref code_mirror_translated, + ) = ccfw.source + else { + panic!("Unexpected diff value."); + }; + // Send a diff if possible. + let client_contents = if self.sent_full { + self.diff_code_mirror( + ccfw.metadata.clone(), + self.version, + ccfw.version, + code_mirror_translated, + ) } else { - false + self.sent_full = true; + ccfw.clone() }; - queue_send!(to_ide_tx.send(EditorMessage { - id: client_message.id, - message: EditorMessageContents::CurrentFile(file_path_string.to_string(), Some(is_text)) + queue_send_func!(self.to_client_tx.send(EditorMessage { + id: ide_message.id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), + contents: Some(client_contents), + cursor_position: update.cursor_position, + scroll_position: update.scroll_position, + }), })); - current_file = file_path; - // Mark the IDE as out of sync, since this - // is a new file. - sync_state = SyncState::OutOfSync; - Ok(()) + // Update to the latest code after + // computing diffs. To avoid ownership + // problems, re-define `ccfw_source_plain`. + let CodeMirrorDiffable::Plain( + code_mirror_translated, + ) = ccfw.source + else { + panic!("{}", "Unexpected diff value."); + }; + self.source_code = code_mirror.doc; + self.code_mirror_doc = code_mirror_translated.doc; + self.code_mirror_doc_blocks = + Some(code_mirror_translated.doc_blocks); + // Update to the version of the file just sent. + self.version = contents.version; + Ok(ResultOkTypes::Void) + } + // TODO + TranslationResultsString::Binary => { + Err(ResultErrTypes::TodoBinarySupport) + } + TranslationResultsString::Unknown => { + // Send the new raw contents. + debug!("Sending translated contents to Client."); + queue_send_func!(self.to_client_tx.send(EditorMessage { + id: ide_message.id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), + contents: Some(CodeChatForWeb { + metadata: SourceFileMetadata { + // Since this is raw data, `mode` doesn't + // matter. + mode: "".to_string(), + }, + source: CodeMirrorDiffable::Plain(CodeMirror { + doc: code_mirror.doc, + doc_blocks: vec![] + }), + version: contents.version + }), + cursor_position: update.cursor_position, + scroll_position: update.scroll_position, + }), + })); + Ok(ResultOkTypes::Void) + } + TranslationResultsString::Toc(_) => { + Err(ResultErrTypes::NotToc) } } } - }; - if let Err(msg) = result { - error!("{msg}"); - send_response(&to_client_tx, client_message.id, Err(msg)).await; } } } - }, - - else => break + } } } + }; + // If there's an error, then report it; + // otherwise, the message is passed to the + // Client, which will provide the result. + if let Err(err) = &result { + error!("{err:?}"); + send_response(&self.to_ide_tx, ide_message.id, result).await; } - debug!("VSCode processing task shutting down."); - if app_state_task - .processing_task_queue_tx - .lock() - .unwrap() - .remove(&connection_id) - .is_none() - { - error!("Unable to remove connection ID {connection_id} from processing task queue."); - } - if app_state_task - .client_queues - .lock() - .unwrap() - .remove(&connection_id) - .is_none() - { - error!("Unable to remove connection ID {connection_id} from client queues."); - } - if app_state_task - .ide_queues - .lock() - .unwrap() - .remove(&connection_id) - .is_none() - { - error!("Unable to remove connection ID {connection_id} from IDE queues."); + true + } + + /// Return a `CodeChatForWeb` struct containing a diff between `self.code_mirror_doc` / `self.code_mirror_doc_blocks` and `code_mirror_translated`. + fn diff_code_mirror( + &self, + // The `metadata` and `version` fields will be copied from this to the returned `CodeChatForWeb` struct. + metadata: SourceFileMetadata, + // The version number of the previous (before) data. Typically, `self.version`. + before_version: f64, + // The version number for the resulting return struct. + version: f64, + // This provides the after data for the diff; before data comes from `self.code_mirror` / `self.code_mirror_doc`. + code_mirror_after: &CodeMirror, + ) -> CodeChatForWeb { + assert!(self.sent_full); + let doc_diff = diff_str(&self.code_mirror_doc, &code_mirror_after.doc); + let Some(ref cmdb) = self.code_mirror_doc_blocks else { + panic!("Should have diff of doc blocks!"); + }; + let doc_blocks_diff = diff_code_mirror_doc_blocks(cmdb, &code_mirror_after.doc_blocks); + CodeChatForWeb { + // Clone needed here, so we can copy it + // later. + metadata, + source: CodeMirrorDiffable::Diff(CodeMirrorDiff { + doc: doc_diff, + doc_blocks: doc_blocks_diff, + // The diff was made between the before version (this) and the after version (`ccfw.version`). + version: before_version, + }), + version, } + } - from_ide_rx.close(); - from_ide_rx.close(); + async fn client_update(&mut self, client_message: EditorMessage) -> bool { + let EditorMessageContents::Update(update_message_contents) = client_message.message else { + panic!("Expected update message."); + }; + debug!("Forwarding translation of it to the IDE."); + match try_canonicalize(&update_message_contents.file_path) { + Err(err) => { + let err = ResultErrTypes::TryCanonicalizeError(err.to_string()); + error!("{err:?}"); + send_response(&self.to_client_tx, client_message.id, Err(err)).await; + return true; + } + Ok(clean_file_path) => { + let codechat_for_web = match update_message_contents.contents { + None => None, + Some(cfw) => match codechat_for_web_to_source(&cfw) { + Ok(new_source_code) => { + // Update the stored CodeMirror data structures with what we just received. This must be updated before we can translate back to check for changes (the next step). + let CodeMirrorDiffable::Plain(code_mirror) = cfw.source else { + // TODO: support diffable! + panic!("Diff not supported."); + }; + self.code_mirror_doc = code_mirror.doc; + self.code_mirror_doc_blocks = Some(code_mirror.doc_blocks); + // We may need to change this version if we send a diff back to the Client. + let mut cfw_version = cfw.version; - // Drain any remaining messages after closing the queue. - while let Some(m) = from_ide_rx.recv().await { - warn!("Dropped queued message {m:?}"); - } - while let Some(m) = from_client_rx.recv().await { - warn!("Dropped queued message {m:?}"); + // Translate back to the Client to see if there are any changes after this conversion. Only check CodeChat documents, not Markdown docs. + if cfw.metadata.mode != MARKDOWN_MODE + && let Ok(ccfws) = source_to_codechat_for_web_string( + &new_source_code, + &clean_file_path, + cfw.version, + false, + ) + && let TranslationResultsString::CodeChat(ccfw) = ccfws.0 + && let CodeMirrorDiffable::Plain(ref code_mirror_translated) = + ccfw.source + && self.sent_full + { + // Determine if the re-translation includes changes (such as line wrapping in doc blocks which changes line numbering, creation of a new doc block from previous code block text, or updates from future document intelligence such as renamed headings, etc.) For doc blocks that haven't been edited by TinyMCE, this is easy; equality is sufficient. Doc blocks that have been edited are a different case: TinyMCE removes newlines, causing a lot of "changes" to re-insert these. Therefore, use the following approach: + // + // 1. Compare the `doc` values. If they differ, then the the Client needs an update. + // 2. Compare each code block using simple equality. If this fails, compare the doc block text excluding newlines. If still different, then the Client needs an update. + if code_mirror_translated.doc != self.code_mirror_doc + || !doc_block_compare( + &code_mirror_translated.doc_blocks, + self.code_mirror_doc_blocks.as_ref().unwrap(), + ) + { + // Use a whole number to avoid encoding differences with fractional values. + cfw_version = random::() as f64; + // The Client needs an update. + let client_contents = self.diff_code_mirror( + cfw.metadata.clone(), + cfw.version, + cfw_version, + code_mirror_translated, + ); + queue_send_func!(self.to_client_tx.send(EditorMessage { + id: self.id, + message: EditorMessageContents::Update( + UpdateMessageContents { + file_path: update_message_contents.file_path, + contents: Some(client_contents), + // Don't change the current position, since the Client editing position should be left undisturbed. + cursor_position: None, + scroll_position: None + } + ) + })); + self.id += MESSAGE_ID_INCREMENT; + } + }; + // Correct EOL endings for use with the + // IDE. + let new_source_code_eol = eol_convert(new_source_code, &self.eol); + let ccfw = if self.sent_full && self.allow_source_diffs { + Some(CodeChatForWeb { + metadata: cfw.metadata, + source: CodeMirrorDiffable::Diff(CodeMirrorDiff { + // Diff with correct EOLs, so that (for + // CRLF files as well as LF files) offsets + // are correct. + doc: diff_str(&self.source_code, &new_source_code_eol), + doc_blocks: vec![], + version: self.version, + }), + version: cfw_version, + }) + } else { + Some(CodeChatForWeb { + metadata: cfw.metadata, + source: CodeMirrorDiffable::Plain(CodeMirror { + // We must clone here, so that it can be + // placed in the TX queue. + doc: new_source_code_eol.clone(), + doc_blocks: vec![], + }), + version: cfw_version, + }) + }; + self.version = cfw_version; + self.source_code = new_source_code_eol; + ccfw + } + Err(message) => { + let err = ResultErrTypes::CannotTranslateCodeChat(message.to_string()); + error!("{err:?}"); + send_response(&self.to_client_tx, client_message.id, Err(err)).await; + return true; + } + }, + }; + queue_send_func!(self.to_ide_tx.send(EditorMessage { + id: client_message.id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: clean_file_path.to_str().expect("Since the path started as a string, assume it losslessly translates back to a string.").to_string(), + contents: codechat_for_web, + cursor_position: update_message_contents.cursor_position, + scroll_position: update_message_contents.scroll_position, + }) + })); + } } - debug!("VSCode processing task exited."); + + true } } @@ -995,6 +1135,33 @@ fn eol_convert(s: String, eol_type: &EolType) -> String { } } +// Given a vector of two doc blocks, compare them, ignoring newlines. +fn doc_block_compare(a: &CodeMirrorDocBlockVec, b: &CodeMirrorDocBlockVec) -> bool { + if a.len() != b.len() { + return false; + } + + a.iter().zip(b).all(|el| { + let a = el.0; + let b = el.1; + a.from == b.from + && a.to == b.to + && a.indent == b.indent + && a.delimiter == b.delimiter + && (a.contents == b.contents + // TinyMCE replaces newlines inside paragraphs with a space; for a crude comparison, translate all newlines back to spaces, then ignore leading/trailing newlines. + || map_newlines_to_spaces(&a.contents).eq(map_newlines_to_spaces(&b.contents))) + }) +} + +fn map_newlines_to_spaces<'a>( + s: &'a str, +) -> std::iter::Map, impl FnMut(char) -> char> { + s.trim() + .chars() + .map(|c: char| if c == '\n' { ' ' } else { c }) +} + // Provide a simple debug function that prints only the first // `MAX_MESSAGE_LENGTH` characters of the provided value. fn debug_shorten(val: T) -> String { @@ -1010,3 +1177,29 @@ fn debug_shorten(val: T) -> String { "".to_string() } } + +// Tests +// ----- +#[cfg(test)] +mod tests { + use crate::{processing::CodeMirrorDocBlock, translation::doc_block_compare}; + + #[test] + fn test_x1() { + let before = vec![CodeMirrorDocBlock { + from: 0, + to: 20, + indent: "".to_string(), + delimiter: "//".to_string(), + contents: "

Copyright (C) 2025 Bryan A. Jones.

\n

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.

\n

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.

\n

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.

\n

debug_enable.mts -- Configure debug features

\n

True to enable additional debug logging.

".to_string(), + }]; + let after = vec![CodeMirrorDocBlock { + from: 0, + to: 20, + indent: "".to_string(), + delimiter: "//".to_string(), + contents: "

Copyright (C) 2025 Bryan A. Jones.

\n

This file is part of the CodeChat Editor. The CodeChat Editor is free\nsoftware: you can redistribute it and/or modify it under the terms of the GNU\nGeneral Public License as published by the Free Software Foundation, either\nversion 3 of the License, or (at your option) any later version.

\n

The CodeChat Editor is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\nFITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more\ndetails.

\n

You should have received a copy of the GNU General Public License along with\nthe CodeChat Editor. If not, see\nhttp://www.gnu.org/licenses.

\n

debug_enable.mts -- Configure debug features

\n

True to enable additional debug logging.

\n".to_string(), + }]; + assert!(doc_block_compare(&before, &after)); + } +} diff --git a/server/src/webserver.rs b/server/src/webserver.rs index 35809b5f..baef0294 100644 --- a/server/src/webserver.rs +++ b/server/src/webserver.rs @@ -14,14 +14,14 @@ // the CodeChat Editor. If not, see // [http://www.gnu.org/licenses](http://www.gnu.org/licenses). /// `webserver.rs` -- Serve CodeChat Editor Client webpages -/// ======================================================= +/// ============================================================================ // Submodules -// ---------- +// ----------------------------------------------------------------------------- #[cfg(test)] pub mod tests; // Imports -// ------- +// ----------------------------------------------------------------------------- // // ### Standard library use std::{ @@ -30,6 +30,7 @@ use std::{ net::SocketAddr, path::{self, MAIN_SEPARATOR_STR, Path, PathBuf}, str::FromStr, + string::FromUtf8Error, sync::{Arc, Mutex}, time::Duration, }; @@ -76,18 +77,24 @@ use url::Url; // ### Local //use crate::capture::EventCapture; -use crate::ide::filewatcher::{ - filewatcher_browser_endpoint, filewatcher_client_endpoint, filewatcher_root_fs_redirect, - filewatcher_websocket, -}; -use crate::ide::vscode::vscode_ide_websocket; -use crate::ide::vscode::{serve_vscode_fs, vscode_client_framework, vscode_client_websocket}; -use crate::processing::{ - CodeChatForWeb, TranslationResultsString, find_path_to_toc, source_to_codechat_for_web_string, +use crate::{ + ide::{ + filewatcher::{ + filewatcher_browser_endpoint, filewatcher_client_endpoint, + filewatcher_root_fs_redirect, filewatcher_websocket, + }, + vscode::{ + serve_vscode_fs, vscode_client_framework, vscode_client_websocket, vscode_ide_websocket, + }, + }, + processing::{ + CodeChatForWeb, SourceToCodeChatForWebError, TranslationResultsString, find_path_to_toc, + source_to_codechat_for_web_string, + }, }; // Data structures -// --------------- +// ----------------------------------------------------------------------------- // // ### Data structures supporting a websocket connection between the IDE, this // @@ -141,9 +148,8 @@ pub enum SimpleHttpResponse { Bin(PathBuf), } -// List all the possible errors when responding to an HTTP request. See [The -// definitive guide to error handling in -// Rust](https://www.howtocodeit.com/articles/the-definitive-guide-to-rust-error-handling). +// List all the possible errors when responding to an HTTP request. See +// [The definitive guide to error handling in Rust](https://www.howtocodeit.com/articles/the-definitive-guide-to-rust-error-handling). #[derive(Debug, thiserror::Error)] pub enum SimpleHttpResponseError { #[error("Error opening file")] @@ -157,7 +163,7 @@ pub enum SimpleHttpResponseError { #[error("Bundled file {0} not found.")] BundledFileNotFound(String), #[error("Lexer error: {0}.")] - LexerError(String), + LexerError(#[from] SourceToCodeChatForWebError), } /// Define the data structure used to pass data between the CodeChat Editor @@ -230,20 +236,73 @@ pub enum EditorMessageContents { /// The contents of a `Result` message. We can't export this type, since `ts-rs` /// only supports structs and enums. -type MessageResult = Result< +pub type MessageResult = Result< // The result of the operation, if successful. ResultOkTypes, // The error message. - String, + ResultErrTypes, >; #[derive(Debug, Serialize, Deserialize, PartialEq, TS)] pub enum ResultOkTypes { /// Most messages have no result. Void, - /// The `LoadFile` message provides file contents, if available. This + /// The `LoadFile` message provides file contents and a revision number, if available. This /// message may only be sent from the IDE to the Server. - LoadFile(Option), + LoadFile(Option<(String, f64)>), +} + +#[derive(Debug, Serialize, Deserialize, TS, PartialEq, thiserror::Error)] +pub enum ResultErrTypes { + #[error("File out of sync; update rejected")] + OutOfSync, + #[error("IDE must not send this message")] + IdeIllegalMessage, + #[error("Client not allowed to send this message")] + ClientIllegalMessage, + #[error("Client must not receive this message: {0}")] + ClientIllegalMessageReceived(String), + #[error("timeout: message id {0} unacknowledged")] + MessageTimeout(f64), + #[error("unable to convert path {0:?} to string")] + NoPathToString(PathBuf), + // We can't pass the full error, since it's not serializable. + #[error("unable to convert URL {0} to path: {1}")] + UrlToPathError(String, String), + #[error("unable to canonicalize path: {0}")] + TryCanonicalizeError(String), + #[error("source incorrectly recognized as a TOC")] + NotToc, + #[error("unable to translate source to CodeChat: {0}")] + CannotTranslateSource(String), + #[error("unable to translate CodeChat to source: {0}")] + CannotTranslateCodeChat(String), + #[error("TODO: support for updates with diffable sources")] + TodoDiffSupport, + #[error("TODO: support for binary files")] + TodoBinarySupport, + #[error("unable to open web browser: {0}")] + WebBrowserOpenFailed(String), + #[error("unexpected message {0}")] + UnexpectedMessage(String), + #[error("invalid IDE type: {0:?}")] + InvalidIdeType(IdeType), + #[error("update for file '{0}' doesn't match current file '{1:?}'")] + WrongFileUpdate(String, Option), + #[error("file watcher error: {0}")] + FileWatchingError(String), + #[error("unable to unwatch file '{0}': {1}")] + FileUnwatchError(PathBuf, String), + #[error("unable to save file '{0}': {1}")] + SaveFileError(PathBuf, String), + #[error("unable to watch file '{0}': {1}")] + FileWatchError(PathBuf, String), + #[error("ignoring update for {0} because it's not the current file {1}")] + IgnoredUpdate(String, String), + #[error("no open document for {0}")] + NoOpenDocument(String), + #[error("unable to open file {0}: {1}")] + OpenFileFailed(String, String), } /// Specify the type of IDE that this client represents. @@ -269,7 +328,8 @@ pub struct UpdateMessageContents { /// The contents of this file. #[serde(skip_serializing_if = "Option::is_none")] pub contents: Option, - /// The line in the file where the cursor is located. TODO: Selections are not yet supported. + /// The line in the file where the cursor is located. TODO: Selections are + /// not yet supported. #[serde(skip_serializing_if = "Option::is_none")] pub cursor_position: Option, /// The line at the top of the screen. @@ -309,19 +369,18 @@ pub struct Credentials { } // Macros -// ------ +// ----------------------------------------------------------------------------- /// Create a macro to report an error when enqueueing an item. #[macro_export] -macro_rules! oneshot_send { - // Provide two options: `break` or `break 'label`. +macro_rules! queue_send { ($tx: expr) => { - if let Err(err) = $tx { + if let Err(err) = $tx.await { error!("Unable to enqueue: {err:?}"); break; } }; ($tx: expr, $label: tt) => { - if let Err(err) = $tx { + if let Err(err) = $tx.await { error!("Unable to enqueue: {err:?}"); break $label; } @@ -329,17 +388,17 @@ macro_rules! oneshot_send { } #[macro_export] -macro_rules! queue_send { +macro_rules! queue_send_func { ($tx: expr) => { - $crate::oneshot_send!($tx.await) - }; - ($tx: expr, $label: tt) => { - $crate::oneshot_send!($tx.await, $label) + if let Err(err) = $tx.await { + error!("Unable to enqueue: {err:?}"); + return false; + } }; } /// Globals -/// ------- +/// ---------------------------------------------------------------------------- // The timeout for a reply from a websocket, in ms. Use a short timeout to speed // up unit tests. pub const REPLY_TIMEOUT_MS: Duration = if cfg!(test) { @@ -412,8 +471,8 @@ const MATHJAX_TAGS: &str = concatdoc!( }, }; "#, - // Per the [MathJax - // docs](https://docs.mathjax.org/en/latest/web/components/combined.html#tex-chtml), + // Per the + // [MathJax docs](https://docs.mathjax.org/en/latest/web/components/combined.html#tex-chtml), // enable tex input and HTML output. r#" "# @@ -490,7 +549,7 @@ pub fn set_root_path( } // Webserver functionality -// ----------------------- +// ----------------------------------------------------------------------------- #[get("/ping")] async fn ping() -> HttpResponse { HttpResponse::Ok().body("pong") @@ -699,8 +758,8 @@ pub async fn filesystem_endpoint( pub async fn try_read_as_text(file: &mut File) -> Option { let mut file_contents = String::new(); // TODO: this is a rather crude way to detect if a file is binary. It's - // probably slow for large file (the [underlying - // code](https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/util/read_to_string.rs#L57) + // probably slow for large file (the + // [underlying code](https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/util/read_to_string.rs#L57) // looks like it reads the entire file to memory, then converts that to // UTF-8). Find a heuristic sniffer instead, such as // [libmagic](https://docs.rs/magic/0.13.0-alpha.3/magic/). @@ -718,6 +777,8 @@ pub async fn try_read_as_text(file: &mut File) -> Option { pub async fn file_to_response( // The HTTP request presented to the processing task. http_request: &ProcessingTaskHttpRequest, + // The version of this file. + version: f64, // Path to the file currently being edited. This path should be cleaned by // `try_canonicalize`. current_filepath: &Path, @@ -774,25 +835,35 @@ pub async fn file_to_response( // `try_canonical`. let is_current_file = file_path == current_filepath; let is_toc = http_request.flags == ProcessingTaskHttpRequestFlags::Toc; - let (translation_results_string, path_to_toc) = if let Some(file_contents_text) = file_contents - { + let translation_results = if let Some(file_contents_text) = file_contents { if is_current_file || is_toc { source_to_codechat_for_web_string( // Ensure we work with Unix-style (LF only) files, since other // line endings break the translation process. &file_contents_text.replace("\r\n", "\n"), file_path, + version, is_toc, ) } else { // If this isn't the current file, then don't parse it. - (TranslationResultsString::Unknown, None) + Ok((TranslationResultsString::Unknown, None)) } } else { - ( + Ok(( TranslationResultsString::Binary, find_path_to_toc(file_path), - ) + )) + }; + let (translation_results_string, path_to_toc) = match translation_results { + // Report a lexer error. + Err(err) => { + return ( + SimpleHttpResponse::Err(SimpleHttpResponseError::LexerError(err)), + None, + ); + } + Ok(tr) => tr, }; let is_project = path_to_toc.is_some(); // For project files, add in the sidebar. Convert this from a Windows path @@ -862,10 +933,6 @@ pub async fn file_to_response( None, ); } - // Report a lexer error. - TranslationResultsString::Err(err_string) => { - return (SimpleHttpResponse::Err(SimpleHttpResponseError::LexerError(err_string)), None); - } // This is a CodeChat file. The following code wraps the CodeChat for // web results in a CodeChat Editor Client webpage. TranslationResultsString::CodeChat(codechat_for_web) => codechat_for_web, @@ -989,8 +1056,8 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S let path_to_toc = escape_html(path_to_toc); SimpleHttpResponse::Ok( - // The JavaScript is a stripped-down version of [on\_navigate from - // CodeChatEditor.mts](../../client/src/CodeChatEditor.mts). + // The JavaScript is a stripped-down version of + // [on\_navigate from CodeChatEditor.mts](../../client/src/CodeChatEditor.mts). formatdoc!( r#" @@ -1035,7 +1102,7 @@ fn make_simple_viewer(http_request: &ProcessingTaskHttpRequest, html: &str) -> S } /// Websockets -/// ---------- +/// ---------------------------------------------------------------------------- /// /// Each CodeChat Editor IDE instance pairs with a CodeChat Editor Client /// through the CodeChat Editor Server. Together, these form a joint editor, @@ -1081,21 +1148,20 @@ pub fn client_websocket( // then the other websocket should also be immediately closed (also case // 2). // - // 1. The IDE plugin needs to close. - // 1. The IDE plugin sends a `Closed` message. - // 2. The Client replies with a `Result` message, acknowledging the - // close. It sends an `Update` message if necessary to save the - // current file. - // 3. After receiving the acknowledge from the Update message (if - // sent), the Client closes the websocket. The rest of this - // sequence is covered in the next case. - // 2. Either websocket is closed. In this case, the other websocket - // should be immediately closed; there's no longer the - // opportunity to perform a more controlled shutdown (see the - // first case). - // 1. The websocket which closed enqueues a `Closed` message for - // the other websocket. - // 2. When the other websocket receives this message, it closes. + // 1. The IDE plugin needs to close. + // 1. The IDE plugin sends a `Closed` message. + // 2. The Client replies with a `Result` message, acknowledging the + // close. It sends an `Update` message if necessary to save the + // current file. + // 3. After receiving the acknowledge from the Update message (if + // sent), the Client closes the websocket. The rest of this + // sequence is covered in the next case. + // 2. Either websocket is closed. In this case, the other websocket + // should be immediately closed; there's no longer the opportunity to + // perform a more controlled shutdown (see the first case). + // 1. The websocket which closed enqueues a `Closed` message for the + // other websocket. + // 2. When the other websocket receives this message, it closes. // // True when the websocket's client deliberately closes the websocket; // otherwise, closing represents a network interruption (such as the @@ -1171,11 +1237,11 @@ pub fn client_websocket( EditorMessageContents::LoadFile(_) | EditorMessageContents::ClientHtml(_) | EditorMessageContents::Closed => { - let msg = format!("Invalid message {joint_message:?}"); - error!("{msg}"); + let err = ResultErrTypes::ClientIllegalMessage; + error!("{err}"); queue_send!(from_websocket_tx.send(EditorMessage { id: joint_message.id, - message: EditorMessageContents::Result(Err(msg)) + message: EditorMessageContents::Result(Err(err)) })); }, @@ -1232,14 +1298,14 @@ pub fn client_websocket( let timeout_tx = from_websocket_tx.clone(); let waiting_task = actix_rt::spawn(async move { sleep(REPLY_TIMEOUT_MS).await; - let msg = format!("Timeout: message id {} unacknowledged.", m.id); - error!("{msg}"); + let err = ResultErrTypes::MessageTimeout(m.id); + error!("{err}"); // Since the websocket failed to send a // `Result`, produce a timeout `Result` for it. 'timeout: { queue_send!(timeout_tx.send(EditorMessage { id: m.id, - message: EditorMessageContents::Result(Err(msg)) + message: EditorMessageContents::Result(Err(err)) }), 'timeout); } }); @@ -1301,7 +1367,7 @@ pub fn client_websocket( } // Webserver core -// -------------- +// ----------------------------------------------------------------------------- #[actix_web::main] pub async fn main( extension_base_path: Option<&Path>, @@ -1470,7 +1536,7 @@ where } // Utilities -// --------- +// ----------------------------------------------------------------------------- // // Send a response to the client after processing a message from the client. pub async fn send_response(client_tx: &Sender, id: f64, result: MessageResult) { @@ -1485,6 +1551,20 @@ pub async fn send_response(client_tx: &Sender, id: f64, result: M } } +#[derive(Debug, thiserror::Error)] +pub enum UrlToPathError { + #[error("unable to parse URL")] + ParseError(#[from] url::ParseError), + #[error("URL {0} cannot be a base.")] + NotBase(String), + #[error("URL {0} has incorrect prefix.")] + IncorrectPrefix(String), + #[error("unable to decode URL")] + UnableToDecode(#[from] FromUtf8Error), + #[error(transparent)] + UrlNotFile(#[from] TryCanonicalizeError), +} + // Convert a URL referring to a file in the filesystem into the path to that // file. pub fn url_to_path( @@ -1495,13 +1575,12 @@ pub fn url_to_path( expected_prefix: &[&str], // Output: the resulting path to the file, or a string explaining why an // error occurred during conversion. -) -> Result { +) -> Result { // Parse to a URL, then split it to path segments. - let url = Url::parse(url_string) - .map_err(|e| format!("Error: unable to parse URL {url_string}: {e}"))?; + let url = Url::parse(url_string)?; let path_segments_vec: Vec<_> = url .path_segments() - .ok_or_else(|| format!("Error: URL {url} cannot be a base."))? + .ok_or_else(|| UrlToPathError::NotBase(url_string.to_string()))? .collect(); // Make sure the path segments start with the `expected_prefix`. @@ -1512,7 +1591,7 @@ pub fn url_to_path( // The URL should have at least the expected prefix plus one more element // (the connection ID). if path_segments_vec.len() < expected_prefix.len() + 1 || !prefix_equal { - return Err(format!("Error: URL {url} has incorrect prefix.")); + return Err(UrlToPathError::IncorrectPrefix(url_string.to_string())); } // Strip the expected prefix; the remainder is a file path. @@ -1525,10 +1604,10 @@ pub fn url_to_path( .iter() .map(|path_segment| { urlencoding::decode(path_segment) - .map_err(|e| format!("Error: unable to decode URL {url_string}: {e}.")) + .map_err(UrlToPathError::UnableToDecode) .map(|path_seg| path_seg.replace("\\", "%5C")) }) - .collect::, String>>()?; + .collect::, UrlToPathError>>()?; // Join the segments into a path. let path_str = path_segments_suffix_decoded.join(MAIN_SEPARATOR_STR); @@ -1538,21 +1617,29 @@ pub fn url_to_path( #[cfg(not(target_os = "windows"))] let path_str = "/".to_string() + &path_str; - try_canonicalize(&path_str) + try_canonicalize(&path_str).map_err(UrlToPathError::UrlNotFile) } +#[derive(Debug, thiserror::Error)] +pub enum TryCanonicalizeError { + #[error("unable to parse {file_path} into file path: {error}")] + ParseFailure { file_path: String, error: String }, + #[error("unable to make file path absolute")] + CannotAbsolute(#[from] io::Error), +} // Given a string representing a file, transform it into a `PathBuf`. Correct it // as much as possible: // -// 1. Convert Linux path separators to this platform's path separators. -// 2. If the file exists and if this is Windows, correct case based on the -// actual file's naming (even though the filesystem is case-insensitive; -// this makes comparisons in the TypeScript simpler). -pub fn try_canonicalize(file_path: &str) -> Result { +// 1. Convert Linux path separators to this platform's path separators. +// 2. If the file exists and if this is Windows, correct case based on the +// actual file's naming (even though the filesystem is case-insensitive; this +// makes comparisons in the TypeScript simpler). +pub fn try_canonicalize(file_path: &str) -> Result { match PathBuf::from_str(file_path) { - Err(err) => Err(format!( - "Error: unable to parse file path {file_path}: {err}." - )), + Err(err) => Err(TryCanonicalizeError::ParseFailure { + file_path: file_path.to_string(), + error: err.to_string(), + }), Ok(path_buf) => match path_buf.canonicalize() { Ok(p) => Ok(PathBuf::from(simplified(&p))), // [Canonicalize](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html#errors) @@ -1568,7 +1655,7 @@ pub fn try_canonicalize(file_path: &str) -> Result { Err(_) => { if path_buf.is_absolute() { match path::absolute(&path_buf) { - Err(err) => Err(format!("Unable to make {path_buf:?} absolute: {err}")), + Err(err) => Err(TryCanonicalizeError::CannotAbsolute(err)), Ok(p) => Ok(p), } } else { @@ -1675,8 +1762,8 @@ pub async fn get_server_url(port: u16) -> Result { if env::var("CODESPACES") == Ok("true".to_string()) { let codespace_name = env::var("CODESPACE_NAME")?; let codespace_domain = env::var("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN")?; - // Use the GitHub CLI to [forward this - // port](https://docs.github.com/en/codespaces/developing-in-a-codespace/using-github-codespaces-with-github-cli#modify-ports-in-a-codespace). + // Use the GitHub CLI to + // [forward this port](https://docs.github.com/en/codespaces/developing-in-a-codespace/using-github-codespaces-with-github-cli#modify-ports-in-a-codespace). let status = Command::new("gh") .args([ "codespace", diff --git a/server/src/webserver/tests.rs b/server/src/webserver/tests.rs index b79dac8f..9cc41e2c 100644 --- a/server/src/webserver/tests.rs +++ b/server/src/webserver/tests.rs @@ -17,18 +17,23 @@ /// ================================================ // Imports // ------- -use std::{ - path::{MAIN_SEPARATOR_STR, PathBuf}, - thread::{self, sleep}, - time::Duration, -}; +// ### Standard library +use std::path::{MAIN_SEPARATOR_STR, PathBuf}; +#[cfg(not(target_os = "macos"))] +use std::{thread::sleep, time::Duration}; +// ### Third-party +#[cfg(not(target_os = "macos"))] use assert_cmd::Command; use assertables::{assert_ends_with, assert_not_contains, assert_starts_with}; +// ### Local use super::{path_to_url, url_to_path}; -use crate::ide::{filewatcher::FILEWATCHER_PATH_PREFIX, vscode::tests::IP_PORT}; use crate::prep_test_dir; +use crate::{ + cast, + ide::{filewatcher::FILEWATCHER_PATH_PREFIX, vscode::tests::IP_PORT}, +}; // Support functions // ----------------- @@ -42,6 +47,7 @@ use crate::prep_test_dir; // warning: use of deprecated associated function `assert_cmd::Command::cargo_bin`: // incompatible with a custom cargo build-dir, see instead `cargo::cargo_bin_cmd!` // ``` +#[cfg(not(target_os = "macos"))] #[allow(deprecated)] fn get_server() -> Command { Command::cargo_bin(assert_cmd::pkg_name!()).unwrap() @@ -55,32 +61,38 @@ fn test_url_to_path() { // Test a non-existent path. assert_eq!( - url_to_path( - &format!( - "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{}path%20spaces/foo.py", - if cfg!(windows) { "C:/" } else { "" } + cast!( + url_to_path( + &format!( + "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{}path%20spaces/foo.py", + if cfg!(windows) { "C:/" } else { "" } + ), + FILEWATCHER_PATH_PREFIX ), - FILEWATCHER_PATH_PREFIX + Ok ), - Ok(PathBuf::from(format!( + PathBuf::from(format!( "{}path spaces{MAIN_SEPARATOR_STR}foo.py", if cfg!(windows) { "C:\\" } else { "/" } - ),)) + ),) ); // Test a path with a backslash in it. assert_eq!( - url_to_path( - &format!( - "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{}foo%5Cbar.py", - if cfg!(windows) { "C:/" } else { "" } + cast!( + url_to_path( + &format!( + "http://127.0.0.1:{IP_PORT}/fw/fsc/dummy_connection_id/{}foo%5Cbar.py", + if cfg!(windows) { "C:/" } else { "" } + ), + FILEWATCHER_PATH_PREFIX ), - FILEWATCHER_PATH_PREFIX + Ok ), - Ok(PathBuf::from(format!( + PathBuf::from(format!( "{}foo%5Cbar.py", if cfg!(windows) { "C:\\" } else { "/" } - ),)) + ),) ); // Test an actual path. @@ -119,29 +131,30 @@ fn test_path_to_url() { temp_dir.close().unwrap(); } -// Test startup outside the repo path. +// Test startup outside the repo path. For some reason, this fails intermittently on Mac. Ignore these failures. +#[cfg(not(target_os = "macos"))] #[test] fn test_other_path() { let (temp_dir, test_dir) = prep_test_dir!(); - // Start the server. - let test_dir1 = test_dir.clone(); - let handle = thread::spawn(move || { - get_server() - .args(["--port", "8083", "start"]) - .current_dir(&test_dir1) - .assert() - .success(); - }); - // The server waits for up to 3 seconds for a ping to work. Add some extra - // time for starting the process. - sleep(Duration::from_millis(6000)); + // Start the server. Calling `output()` causes the program to hang; call + // `status()` instead. Since the `assert_cmd` crates doesn't offer this, + // use the std lib instead. + std::process::Command::new(get_server().get_program()) + .args(["--port", "8083", "start"]) + .current_dir(&test_dir) + .status() + .expect("failed to start server"); + + // Stop it. get_server() .args(["--port", "8083", "stop"]) .current_dir(&test_dir) .assert() .success(); - handle.join().unwrap(); + + // Wait for the server to exit, since it locks the temp_dir. + sleep(Duration::from_millis(3000)); // Report any errors produced when removing the temporary directory. temp_dir.close().unwrap(); diff --git a/server/tests/fixtures/overall/overall_core/test_client_updates/test.py b/server/tests/fixtures/overall/overall_core/test_client_updates/test.py index 9d935e6c..23103ab8 100644 --- a/server/tests/fixtures/overall/overall_core/test_client_updates/test.py +++ b/server/tests/fixtures/overall/overall_core/test_client_updates/test.py @@ -1,4 +1,3 @@ -# Test updates in the client that modify the client after appending to a line. -def foo(): - A comment - print() \ No newline at end of file +# 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/fixtures/overall/overall_core/test_server/test.md b/server/tests/fixtures/overall/overall_core/test_server/test.md index 0a55de74..75c4c898 100644 --- a/server/tests/fixtures/overall/overall_core/test_server/test.md +++ b/server/tests/fixtures/overall/overall_core/test_server/test.md @@ -1 +1,3 @@ -A **markdown** file. \ No newline at end of file +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. \ No newline at end of file diff --git a/server/tests/overall_core/mod.rs b/server/tests/overall_core/mod.rs index ac429bfc..688e88b9 100644 --- a/server/tests/overall_core/mod.rs +++ b/server/tests/overall_core/mod.rs @@ -56,10 +56,11 @@ use std::{ collections::HashMap, env, error::Error, panic::AssertUnwindSafe, path::PathBuf, time::Duration, }; -use assert_fs::TempDir; // ### Third-party +use assert_fs::TempDir; use dunce::canonicalize; use futures::FutureExt; +use indoc::indoc; use pretty_assertions::assert_eq; use thirtyfour::{ By, ChromiumLikeCapabilities, DesiredCapabilities, Key, WebDriver, error::WebDriverError, @@ -71,10 +72,10 @@ use tokio::time::sleep; use code_chat_editor::{ cast, ide::CodeChatEditorServer, + lexer::supported_languages::MARKDOWN_MODE, prep_test_dir, processing::{ - CodeChatForWeb, CodeMirror, CodeMirrorDiff, CodeMirrorDiffable, SourceFileMetadata, - StringDiff, + CodeChatForWeb, CodeMirrorDiff, CodeMirrorDiffable, SourceFileMetadata, StringDiff, }, webserver::{ EditorMessage, EditorMessageContents, INITIAL_CLIENT_MESSAGE_ID, MESSAGE_ID_INCREMENT, @@ -163,6 +164,8 @@ macro_rules! harness { // 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. @@ -192,7 +195,7 @@ macro_rules! harness { // Get the resulting web page text. let opened_id = codechat_server.send_message_opened(true).await.unwrap(); - assert_eq!( + pretty_assertions::assert_eq!( codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), EditorMessage { id: opened_id, @@ -240,6 +243,15 @@ macro_rules! harness { }; } +// Given an `Update` message with contents, get the version. +fn get_version(msg: &EditorMessage) -> f64 { + cast!(&msg.message, EditorMessageContents::Update) + .contents + .as_ref() + .unwrap() + .version +} + // Tests // ----- // @@ -249,7 +261,6 @@ macro_rules! harness { // CodeChat, plain, PDF), use hyperlinks, perform edits on code and doc blocks. mod test1 { use super::*; - use pretty_assertions::assert_eq; harness!(test_server_core); } @@ -289,8 +300,9 @@ async fn test_server_core( .await; // Respond to the load request. + let mut version = 1.0; codechat_server - .send_result_loadfile(server_id, Some("# Test\ncode()".to_string())) + .send_result_loadfile(server_id, Some(("# Test\ncode()".to_string(), version))) .await .unwrap(); @@ -345,16 +357,33 @@ async fn test_server_core( // Focus it. doc_block_contents.click().await.unwrap(); - sleep(Duration::from_millis(100)).await; + // The click produces an updated cursor/scroll location after an autosave delay. + let mut client_id = INITIAL_CLIENT_MESSAGE_ID; + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: None, + cursor_position: Some(1), + scroll_position: Some(1.0) + }) + } + ); + // Refind it, since it's now switched with a TinyMCE editor. let tinymce_contents = driver_ref.find(By::Id("TinyMCE-inst")).await.unwrap(); // Make an edit. tinymce_contents.send_keys("foo").await.unwrap(); // Verify the updated text. - let mut client_id = INITIAL_CLIENT_MESSAGE_ID; + client_id += MESSAGE_ID_INCREMENT; + // Update the version from the value provided by the client, which varies randomly. + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + msg, EditorMessage { id: client_id, message: EditorMessageContents::Update(UpdateMessageContents { @@ -369,22 +398,27 @@ async fn test_server_core( to: Some(7), insert: "# Testfoo\n".to_string() }], - doc_blocks: vec![] - }) + doc_blocks: vec![], + version, + }), + version: client_version, }), cursor_position: Some(1), scroll_position: Some(1.0) }) } ); + version = client_version; codechat_server.send_result(client_id, None).await.unwrap(); // Edit the indent. It should only allow spaces and tabs, rejecting other // edits. doc_block_indent.send_keys(" 123").await.unwrap(); + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); client_id += MESSAGE_ID_INCREMENT; assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + msg, EditorMessage { id: client_id, message: EditorMessageContents::Update(UpdateMessageContents { @@ -397,16 +431,19 @@ async fn test_server_core( doc: vec![StringDiff { from: 0, to: Some(10), - insert: " # Testfoo\n".to_string() + insert: " # Testfoo\n".to_string(), }], - doc_blocks: vec![] - }) + doc_blocks: vec![], + version, + }), + version: client_version, }), cursor_position: Some(1), - scroll_position: Some(1.0) - }) + scroll_position: Some(1.0), + }), } ); + version = client_version; codechat_server.send_result(client_id, None).await.unwrap(); // #### Code block tests @@ -457,8 +494,10 @@ async fn test_server_core( // Verify the updated text. client_id += MESSAGE_ID_INCREMENT; + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + msg, EditorMessage { id: client_id, message: EditorMessageContents::Update(UpdateMessageContents { @@ -473,8 +512,10 @@ async fn test_server_core( to: Some(18), insert: "code()bar".to_string() }], - doc_blocks: vec![] - }) + doc_blocks: vec![], + version, + }), + version: client_version, }), cursor_position: Some(2), scroll_position: Some(1.0) @@ -486,10 +527,11 @@ async fn test_server_core( // #### IDE edits // // Perform IDE edits. + version = 2.0; let ide_id = codechat_server .send_message_update_plain( path_str.clone(), - Some(" # Testfood\ncode()bark".to_string()), + Some((" # Testfood\ncode()bark".to_string(), version)), Some(1), None, ) @@ -509,17 +551,18 @@ async fn test_server_core( let doc_block_contents = driver_ref.find(By::Css(contents_css)).await.unwrap(); assert_eq!( doc_block_contents.inner_html().await.unwrap(), - "

Testfood

\n" + "

Testfood

" ); let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); assert_eq!(code_line.inner_html().await.unwrap(), "code()bark"); // Perform a second edit and verification, to produce a diff sent to the // Client. + version = 3.0; let ide_id = codechat_server .send_message_update_plain( path_str.clone(), - Some(" # food\nbark".to_string()), + Some((" # food\nbark".to_string(), version)), Some(1), None, ) @@ -537,7 +580,7 @@ async fn test_server_core( let doc_block_contents = driver_ref.find(By::Css(contents_css)).await.unwrap(); assert_eq!( doc_block_contents.inner_html().await.unwrap(), - "

food

\n" + "

food

" ); let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); assert_eq!(code_line.inner_html().await.unwrap(), "bark"); @@ -552,30 +595,6 @@ async fn test_server_core( .await .unwrap(); - // Before changing files, the current file will be updated. - client_id += MESSAGE_ID_INCREMENT; - assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), - EditorMessage { - id: client_id, - message: EditorMessageContents::Update(UpdateMessageContents { - file_path: path_str.clone(), - contents: Some(CodeChatForWeb { - metadata: SourceFileMetadata { - mode: "python".to_string() - }, - source: CodeMirrorDiffable::Plain(CodeMirror { - doc: " # food\nbark".to_string(), - doc_blocks: vec![] - }) - }), - cursor_position: Some(1), - scroll_position: Some(1.0) - }) - } - ); - codechat_server.send_result(client_id, None).await.unwrap(); - // These next two messages can come in either order. Work around this. expected_messages.insert(EditorMessage { id: current_file_id, @@ -590,9 +609,13 @@ async fn test_server_core( .assert_all_messages(&codechat_server, TIMEOUT) .await; - // Ask the server to load the file from disk. + // Provide the requested file contents. + version = 4.0; codechat_server - .send_result_loadfile(server_id, None) + .send_result_loadfile( + server_id, + Some(("A **markdown** file.".to_string(), version)), + ) .await .unwrap(); @@ -633,15 +656,17 @@ async fn test_server_core( // Perform edits. body_content.send_keys("foo ").await.unwrap(); client_id += MESSAGE_ID_INCREMENT; + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + msg, EditorMessage { id: client_id, message: EditorMessageContents::Update(UpdateMessageContents { file_path: md_path_str.clone(), contents: Some(CodeChatForWeb { metadata: SourceFileMetadata { - mode: "markdown".to_string() + mode: MARKDOWN_MODE.to_string() }, source: CodeMirrorDiffable::Diff(CodeMirrorDiff { doc: vec![StringDiff { @@ -652,8 +677,10 @@ async fn test_server_core( if cfg!(windows) { "\r\n" } else { "\n" } ), }], - doc_blocks: vec![] - }) + doc_blocks: vec![], + version, + }), + version: client_version, }), cursor_position: None, scroll_position: None @@ -663,10 +690,11 @@ async fn test_server_core( codechat_server.send_result(client_id, None).await.unwrap(); // Perform an IDE edit. + version = 5.0; let ide_id = codechat_server .send_message_update_plain( md_path_str.clone(), - Some("food A **markdown** file.".to_string()), + Some(("food A **markdown** file.".to_string(), version)), Some(1), None, ) @@ -848,7 +876,6 @@ async fn test_server_core( // all pass. mod test2 { use super::*; - use pretty_assertions::assert_eq; harness!(test_client_core); } @@ -996,10 +1023,8 @@ async fn test_client_core( Ok(()) } -/* TODO: fails until self-updates work. mod test3 { use super::*; - use pretty_assertions::assert_eq; harness!(test_client_updates_core); } @@ -1045,8 +1070,23 @@ async fn test_client_updates_core( .await; // Respond to the load request. + let ide_version = 0.0; codechat_server - .send_result_loadfile(server_id, None) + .send_result_loadfile( + server_id, + Some(( + indoc!( + " + # Test updates in the client that modify the client after appending to a line. + def foo(): + A comment + print() + " + ) + .to_string(), + ide_version, + )), + ) .await .unwrap(); @@ -1075,6 +1115,7 @@ async fn test_client_updates_core( message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) } ); + server_id += MESSAGE_ID_INCREMENT * 2.0; // Target the iframe containing the Client. let codechat_iframe = driver_ref.find(By::Css("#CodeChat-iframe")).await.unwrap(); @@ -1093,9 +1134,11 @@ async fn test_client_updates_core( .unwrap(); // Verify the updated text. + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let client_version = get_version(&msg); let mut client_id = INITIAL_CLIENT_MESSAGE_ID; assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + msg, EditorMessage { id: client_id, message: EditorMessageContents::Update(UpdateMessageContents { @@ -1110,24 +1153,41 @@ async fn test_client_updates_core( to: None, insert: "# testing\n".to_string() }], - doc_blocks: vec![] - }) + doc_blocks: vec![], + version: ide_version, + }), + version: client_version, }), cursor_position: Some(1), - scroll_position: Some(0.0) + scroll_position: Some(1.0) }) } ); codechat_server.send_result(client_id, None).await.unwrap(); + client_id += MESSAGE_ID_INCREMENT; - // Move the cursor to code, then check that the position is correct. TODO: - // need access to codemirror in test mode. + // The Server sends the Client a wrapped version of the text; the Client replies with a Result(Ok). + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: server_id, + message: EditorMessageContents::Result(Ok(ResultOkTypes::Void)) + } + ); // Insert a character to check the insertion point. let code_line_css = ".CodeChat-CodeMirror .cm-line"; let code_line = driver_ref.find(By::Css(code_line_css)).await.unwrap(); code_line - .send_keys(Key::Alt + Key::Control + "g") + .send_keys( + Key::Alt + + if cfg!(target_os = "macos") { + Key::Command + } else { + Key::Control + } + + "g", + ) .await .unwrap(); // Enter a line in the dialog that pops up. @@ -1138,13 +1198,29 @@ async fn test_client_updates_core( .send_keys("4" + Key::Enter) .await .unwrap(); + // The cursor movement produces a cursor/scroll position update after an autosave delay. + assert_eq!( + codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + EditorMessage { + id: client_id, + message: EditorMessageContents::Update(UpdateMessageContents { + file_path: path_str.clone(), + contents: None, + cursor_position: Some(4), + scroll_position: Some(1.0) + }) + } + ); + client_id += MESSAGE_ID_INCREMENT; + // Add an indented comment. code_line.send_keys(Key::Home + "# ").await.unwrap(); // This should edit the (new) third line of the file after word wrap: `def // foo():`. - client_id += MESSAGE_ID_INCREMENT; + let msg = codechat_server.get_message_timeout(TIMEOUT).await.unwrap(); + let new_client_version = get_version(&msg); assert_eq!( - codechat_server.get_message_timeout(TIMEOUT).await.unwrap(), + msg, EditorMessage { id: client_id, message: EditorMessageContents::Update(UpdateMessageContents { @@ -1155,15 +1231,17 @@ async fn test_client_updates_core( }, source: CodeMirrorDiffable::Diff(CodeMirrorDiff { doc: vec![StringDiff { - from: 115, - to: Some(131), + from: 100, + to: Some(114), insert: " # A comment\n".to_string() }], - doc_blocks: vec![] - }) + doc_blocks: vec![], + version: client_version, + }), + version: new_client_version, }), cursor_position: Some(4), - scroll_position: Some(0.0) + scroll_position: Some(1.0) }) } ); @@ -1171,4 +1249,3 @@ async fn test_client_updates_core( Ok(()) } - */